Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / examples / ux / ux-all-debug.js
1 /*!
2  * Ext JS Library 3.1.0
3  * Copyright(c) 2006-2009 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(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){\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 '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'">&#160;</div>';\r
342     }\r
343 };\r
344 \r
345 // register ptype\r
346 Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);\r
347 \r
348 // backwards compat\r
349 Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.grid');\r
350 \r
351 if(Ext.isWebKit){\r
352     Ext.grid.GridView.prototype.borderWidth = 0;\r
353 }\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 ? 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             hds, i, len, filter;\r
1942         if (view.mainHd) {\r
1943             hds = view.mainHd.select('td').removeClass(this.filterCls);\r
1944             for (i = 0, len = view.cm.config.length; i < len; i++) {\r
1945                 filter = this.getFilter(view.cm.config[i].dataIndex);\r
1946                 if (filter && filter.active) {\r
1947                     hds.item(i).addClass(this.filterCls);\r
1948                 }\r
1949             }\r
1950         }\r
1951     },\r
1952     \r
1953     /** @private */\r
1954     reload : function () {\r
1955         if (this.local) {\r
1956             this.grid.store.clearFilter(true);\r
1957             this.grid.store.filterBy(this.getRecordFilter());\r
1958         } else {\r
1959             var start,\r
1960                 store = this.grid.store;\r
1961             this.deferredUpdate.cancel();\r
1962             if (this.toolbar) {\r
1963                 start = store.paramNames.start;\r
1964                 if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {\r
1965                     store.lastOptions.params[start] = 0;\r
1966                 }\r
1967             }\r
1968             store.reload();\r
1969         }\r
1970     },\r
1971     \r
1972     /**\r
1973      * Method factory that generates a record validator for the filters active at the time\r
1974      * of invokation.\r
1975      * @private\r
1976      */\r
1977     getRecordFilter : function () {\r
1978         var f = [], len, i;\r
1979         this.filters.each(function (filter) {\r
1980             if (filter.active) {\r
1981                 f.push(filter);\r
1982             }\r
1983         });\r
1984         \r
1985         len = f.length;\r
1986         return function (record) {\r
1987             for (i = 0; i < len; i++) {\r
1988                 if (!f[i].validateRecord(record)) {\r
1989                     return false;\r
1990                 }\r
1991             }\r
1992             return true;\r
1993         };\r
1994     },\r
1995     \r
1996     /**\r
1997      * Adds a filter to the collection and observes it for state change.\r
1998      * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.\r
1999      * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.\r
2000      */\r
2001     addFilter : function (config) {\r
2002         var Cls = this.getFilterClass(config.type),\r
2003             filter = config.menu ? config : (new Cls(config));\r
2004         this.filters.add(filter);\r
2005         \r
2006         Ext.util.Observable.capture(filter, this.onStateChange, this);\r
2007         return filter;\r
2008     },\r
2009 \r
2010     /**\r
2011      * Adds filters to the collection.\r
2012      * @param {Array/Ext.grid.ColumnModel} filters Either an Array of\r
2013      * filter configuration objects or an Ext.grid.ColumnModel.  The columns\r
2014      * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>\r
2015      * property and, if present, will be used as the filter configuration object.   \r
2016      */\r
2017     addFilters : function (filters) {\r
2018         if (filters) {\r
2019             var i, len, filter, cm = false, dI;\r
2020             if (filters instanceof Ext.grid.ColumnModel) {\r
2021                 filters = filters.config;\r
2022                 cm = true;\r
2023             }\r
2024             for (i = 0, len = filters.length; i < len; i++) {\r
2025                 filter = false;\r
2026                 if (cm) {\r
2027                     dI = filters[i].dataIndex;\r
2028                     filter = filters[i].filter || filters[i].filterable;\r
2029                     if (filter){\r
2030                         filter = (filter === true) ? {} : filter;\r
2031                         Ext.apply(filter, {dataIndex:dI});\r
2032                         // filter type is specified in order of preference:\r
2033                         //     filter type specified in config\r
2034                         //     type specified in store's field's type config\r
2035                         filter.type = filter.type || this.store.fields.get(dI).type;  \r
2036                     }\r
2037                 } else {\r
2038                     filter = filters[i];\r
2039                 }\r
2040                 // if filter config found add filter for the column \r
2041                 if (filter) {\r
2042                     this.addFilter(filter);\r
2043                 }\r
2044             }\r
2045         }\r
2046     },\r
2047     \r
2048     /**\r
2049      * Returns a filter for the given dataIndex, if one exists.\r
2050      * @param {String} dataIndex The dataIndex of the desired filter object.\r
2051      * @return {Ext.ux.grid.filter.Filter}\r
2052      */\r
2053     getFilter : function (dataIndex) {\r
2054         return this.filters.get(dataIndex);\r
2055     },\r
2056 \r
2057     /**\r
2058      * Turns all filters off. This does not clear the configuration information\r
2059      * (see {@link #removeAll}).\r
2060      */\r
2061     clearFilters : function () {\r
2062         this.filters.each(function (filter) {\r
2063             filter.setActive(false);\r
2064         });\r
2065     },\r
2066 \r
2067     /**\r
2068      * Returns an Array of the currently active filters.\r
2069      * @return {Array} filters Array of the currently active filters.\r
2070      */\r
2071     getFilterData : function () {\r
2072         var filters = [], i, len;\r
2073 \r
2074         this.filters.each(function (f) {\r
2075             if (f.active) {\r
2076                 var d = [].concat(f.serialize());\r
2077                 for (i = 0, len = d.length; i < len; i++) {\r
2078                     filters.push({\r
2079                         field: f.dataIndex,\r
2080                         data: d[i]\r
2081                     });\r
2082                 }\r
2083             }\r
2084         });\r
2085         return filters;\r
2086     },\r
2087     \r
2088     /**\r
2089      * Function to take the active filters data and build it into a query.\r
2090      * The format of the query depends on the <code>{@link #encode}</code>\r
2091      * configuration:\r
2092      * <div class="mdetail-params"><ul>\r
2093      * \r
2094      * <li><b><tt>false</tt></b> : <i>Default</i>\r
2095      * <div class="sub-desc">\r
2096      * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:\r
2097      * <pre><code>\r
2098 filters[0][field]="someDataIndex"&\r
2099 filters[0][data][comparison]="someValue1"&\r
2100 filters[0][data][type]="someValue2"&\r
2101 filters[0][data][value]="someValue3"&\r
2102      * </code></pre>\r
2103      * </div></li>\r
2104      * <li><b><tt>true</tt></b> : \r
2105      * <div class="sub-desc">\r
2106      * JSON encode the filter data\r
2107      * <pre><code>\r
2108 filters[0][field]="someDataIndex"&\r
2109 filters[0][data][comparison]="someValue1"&\r
2110 filters[0][data][type]="someValue2"&\r
2111 filters[0][data][value]="someValue3"&\r
2112      * </code></pre>\r
2113      * </div></li>\r
2114      * </ul></div>\r
2115      * Override this method to customize the format of the filter query for remote requests.\r
2116      * @param {Array} filters A collection of objects representing active filters and their configuration.\r
2117      *    Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured\r
2118      *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.\r
2119      * @return {Object} Query keys and values\r
2120      */\r
2121     buildQuery : function (filters) {\r
2122         var p = {}, i, f, root, dataPrefix, key, tmp,\r
2123             len = filters.length;\r
2124 \r
2125         if (!this.encode){\r
2126             for (i = 0; i < len; i++) {\r
2127                 f = filters[i];\r
2128                 root = [this.paramPrefix, '[', i, ']'].join('');\r
2129                 p[root + '[field]'] = f.field;\r
2130                 \r
2131                 dataPrefix = root + '[data]';\r
2132                 for (key in f.data) {\r
2133                     p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];\r
2134                 }\r
2135             }\r
2136         } else {\r
2137             tmp = [];\r
2138             for (i = 0; i < len; i++) {\r
2139                 f = filters[i];\r
2140                 tmp.push(Ext.apply(\r
2141                     {},\r
2142                     {field: f.field},\r
2143                     f.data\r
2144                 ));\r
2145             }\r
2146             // only build if there is active filter \r
2147             if (tmp.length > 0){\r
2148                 p[this.paramPrefix] = Ext.util.JSON.encode(tmp);\r
2149             }\r
2150         }\r
2151         return p;\r
2152     },\r
2153     \r
2154     /**\r
2155      * Removes filter related query parameters from the provided object.\r
2156      * @param {Object} p Query parameters that may contain filter related fields.\r
2157      */\r
2158     cleanParams : function (p) {\r
2159         // if encoding just delete the property\r
2160         if (this.encode) {\r
2161             delete p[this.paramPrefix];\r
2162         // otherwise scrub the object of filter data\r
2163         } else {\r
2164             var regex, key;\r
2165             regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');\r
2166             for (key in p) {\r
2167                 if (regex.test(key)) {\r
2168                     delete p[key];\r
2169                 }\r
2170             }\r
2171         }\r
2172     },\r
2173     \r
2174     /**\r
2175      * Function for locating filter classes, overwrite this with your favorite\r
2176      * loader to provide dynamic filter loading.\r
2177      * @param {String} type The type of filter to load ('Filter' is automatically\r
2178      * appended to the passed type; eg, 'string' becomes 'StringFilter').\r
2179      * @return {Class} The Ext.ux.grid.filter.Class \r
2180      */\r
2181     getFilterClass : function (type) {\r
2182         // map the supported Ext.data.Field type values into a supported filter\r
2183         switch(type) {\r
2184             case 'auto':\r
2185               type = 'string';\r
2186               break;\r
2187             case 'int':\r
2188             case 'float':\r
2189               type = 'numeric';\r
2190               break;\r
2191         }\r
2192         return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];\r
2193     }\r
2194 });\r
2195 \r
2196 // register ptype\r
2197 Ext.preg('gridfilters', Ext.ux.grid.GridFilters);\r
2198 Ext.namespace('Ext.ux.grid.filter');\r
2199 \r
2200 /** \r
2201  * @class Ext.ux.grid.filter.Filter\r
2202  * @extends Ext.util.Observable\r
2203  * Abstract base class for filter implementations.\r
2204  */\r
2205 Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, {\r
2206     /**\r
2207      * @cfg {Boolean} active\r
2208      * Indicates the initial status of the filter (defaults to false).\r
2209      */\r
2210     active : false,\r
2211     /**\r
2212      * True if this filter is active.  Use setActive() to alter after configuration.\r
2213      * @type Boolean\r
2214      * @property active\r
2215      */\r
2216     /**\r
2217      * @cfg {String} dataIndex \r
2218      * The {@link Ext.data.Store} dataIndex of the field this filter represents.\r
2219      * The dataIndex does not actually have to exist in the store.\r
2220      */\r
2221     dataIndex : null,\r
2222     /**\r
2223      * The filter configuration menu that will be installed into the filter submenu of a column menu.\r
2224      * @type Ext.menu.Menu\r
2225      * @property\r
2226      */\r
2227     menu : null,\r
2228     /**\r
2229      * @cfg {Number} updateBuffer\r
2230      * Number of milliseconds to wait after user interaction to fire an update. Only supported \r
2231      * by filters: 'list', 'numeric', and 'string'. Defaults to 500.\r
2232      */\r
2233     updateBuffer : 500,\r
2234 \r
2235     constructor : function (config) {\r
2236         Ext.apply(this, config);\r
2237             \r
2238         this.addEvents(\r
2239             /**\r
2240              * @event activate\r
2241              * Fires when an inactive filter becomes active\r
2242              * @param {Ext.ux.grid.filter.Filter} this\r
2243              */\r
2244             'activate',\r
2245             /**\r
2246              * @event deactivate\r
2247              * Fires when an active filter becomes inactive\r
2248              * @param {Ext.ux.grid.filter.Filter} this\r
2249              */\r
2250             'deactivate',\r
2251             /**\r
2252              * @event serialize\r
2253              * Fires after the serialization process. Use this to attach additional parameters to serialization\r
2254              * data before it is encoded and sent to the server.\r
2255              * @param {Array/Object} data A map or collection of maps representing the current filter configuration.\r
2256              * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.\r
2257              */\r
2258             'serialize',\r
2259             /**\r
2260              * @event update\r
2261              * Fires when a filter configuration has changed\r
2262              * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
2263              */\r
2264             'update'\r
2265         );\r
2266         Ext.ux.grid.filter.Filter.superclass.constructor.call(this);\r
2267 \r
2268         this.menu = new Ext.menu.Menu();\r
2269         this.init(config);\r
2270         if(config && config.value){\r
2271             this.setValue(config.value);\r
2272             this.setActive(config.active !== false, true);\r
2273             delete config.value;\r
2274         }\r
2275     },\r
2276 \r
2277     /**\r
2278      * Destroys this filter by purging any event listeners, and removing any menus.\r
2279      */\r
2280     destroy : function(){\r
2281         if (this.menu){\r
2282             this.menu.destroy();\r
2283         }\r
2284         this.purgeListeners();\r
2285     },\r
2286 \r
2287     /**\r
2288      * Template method to be implemented by all subclasses that is to\r
2289      * initialize the filter and install required menu items.\r
2290      * Defaults to Ext.emptyFn.\r
2291      */\r
2292     init : Ext.emptyFn,\r
2293     \r
2294     /**\r
2295      * Template method to be implemented by all subclasses that is to\r
2296      * get and return the value of the filter.\r
2297      * Defaults to Ext.emptyFn.\r
2298      * @return {Object} The 'serialized' form of this filter\r
2299      * @methodOf Ext.ux.grid.filter.Filter\r
2300      */\r
2301     getValue : Ext.emptyFn,\r
2302     \r
2303     /**\r
2304      * Template method to be implemented by all subclasses that is to\r
2305      * set the value of the filter and fire the 'update' event.\r
2306      * Defaults to Ext.emptyFn.\r
2307      * @param {Object} data The value to set the filter\r
2308      * @methodOf Ext.ux.grid.filter.Filter\r
2309      */ \r
2310     setValue : Ext.emptyFn,\r
2311     \r
2312     /**\r
2313      * Template method to be implemented by all subclasses that is to\r
2314      * return <tt>true</tt> if the filter has enough configuration information to be activated.\r
2315      * Defaults to <tt>return true</tt>.\r
2316      * @return {Boolean}\r
2317      */\r
2318     isActivatable : function(){\r
2319         return true;\r
2320     },\r
2321     \r
2322     /**\r
2323      * Template method to be implemented by all subclasses that is to\r
2324      * get and return serialized filter data for transmission to the server.\r
2325      * Defaults to Ext.emptyFn.\r
2326      */\r
2327     getSerialArgs : Ext.emptyFn,\r
2328 \r
2329     /**\r
2330      * Template method to be implemented by all subclasses that is to\r
2331      * validates the provided Ext.data.Record against the filters configuration.\r
2332      * Defaults to <tt>return true</tt>.\r
2333      * @param {Ext.data.Record} record The record to validate\r
2334      * @return {Boolean} true if the record is valid within the bounds\r
2335      * of the filter, false otherwise.\r
2336      */\r
2337     validateRecord : function(){\r
2338         return true;\r
2339     },\r
2340 \r
2341     /**\r
2342      * Returns the serialized filter data for transmission to the server\r
2343      * and fires the 'serialize' event.\r
2344      * @return {Object/Array} An object or collection of objects containing\r
2345      * key value pairs representing the current configuration of the filter.\r
2346      * @methodOf Ext.ux.grid.filter.Filter\r
2347      */\r
2348     serialize : function(){\r
2349         var args = this.getSerialArgs();\r
2350         this.fireEvent('serialize', args, this);\r
2351         return args;\r
2352     },\r
2353 \r
2354     /** @private */\r
2355     fireUpdate : function(){\r
2356         if (this.active) {\r
2357             this.fireEvent('update', this);\r
2358         }\r
2359         this.setActive(this.isActivatable());\r
2360     },\r
2361     \r
2362     /**\r
2363      * Sets the status of the filter and fires the appropriate events.\r
2364      * @param {Boolean} active        The new filter state.\r
2365      * @param {Boolean} suppressEvent True to prevent events from being fired.\r
2366      * @methodOf Ext.ux.grid.filter.Filter\r
2367      */\r
2368     setActive : function(active, suppressEvent){\r
2369         if(this.active != active){\r
2370             this.active = active;\r
2371             if (suppressEvent !== true) {\r
2372                 this.fireEvent(active ? 'activate' : 'deactivate', this);\r
2373             }\r
2374         }\r
2375     }    \r
2376 });/** \r
2377  * @class Ext.ux.grid.filter.BooleanFilter\r
2378  * @extends Ext.ux.grid.filter.Filter\r
2379  * Boolean filters use unique radio group IDs (so you can have more than one!)\r
2380  * <p><b><u>Example Usage:</u></b></p>\r
2381  * <pre><code>    \r
2382 var filters = new Ext.ux.grid.GridFilters({\r
2383     ...\r
2384     filters: [{\r
2385         // required configs\r
2386         type: 'boolean',\r
2387         dataIndex: 'visible'\r
2388 \r
2389         // optional configs\r
2390         defaultValue: null, // leave unselected (false selected by default)\r
2391         yesText: 'Yes',     // default\r
2392         noText: 'No'        // default\r
2393     }]\r
2394 });\r
2395  * </code></pre>\r
2396  */\r
2397 Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2398         /**\r
2399          * @cfg {Boolean} defaultValue\r
2400          * Set this to null if you do not want either option to be checked by default. Defaults to false.\r
2401          */\r
2402         defaultValue : false,\r
2403         /**\r
2404          * @cfg {String} yesText\r
2405          * Defaults to 'Yes'.\r
2406          */\r
2407         yesText : 'Yes',\r
2408         /**\r
2409          * @cfg {String} noText\r
2410          * Defaults to 'No'.\r
2411          */\r
2412         noText : 'No',\r
2413 \r
2414     /**  \r
2415      * @private\r
2416      * Template method that is to initialize the filter and install required menu items.\r
2417      */\r
2418     init : function (config) {\r
2419         var gId = Ext.id();\r
2420                 this.options = [\r
2421                         new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}),\r
2422                         new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})];\r
2423                 \r
2424                 this.menu.add(this.options[0], this.options[1]);\r
2425                 \r
2426                 for(var i=0; i<this.options.length; i++){\r
2427                         this.options[i].on('click', this.fireUpdate, this);\r
2428                         this.options[i].on('checkchange', this.fireUpdate, this);\r
2429                 }\r
2430         },\r
2431         \r
2432     /**\r
2433      * @private\r
2434      * Template method that is to get and return the value of the filter.\r
2435      * @return {String} The value of this filter\r
2436      */\r
2437     getValue : function () {\r
2438                 return this.options[0].checked;\r
2439         },\r
2440 \r
2441     /**\r
2442      * @private\r
2443      * Template method that is to set the value of the filter.\r
2444      * @param {Object} value The value to set the filter\r
2445      */ \r
2446         setValue : function (value) {\r
2447                 this.options[value ? 0 : 1].setChecked(true);\r
2448         },\r
2449 \r
2450     /**\r
2451      * @private\r
2452      * Template method that is to get and return serialized filter data for\r
2453      * transmission to the server.\r
2454      * @return {Object/Array} An object or collection of objects containing\r
2455      * key value pairs representing the current configuration of the filter.\r
2456      */\r
2457     getSerialArgs : function () {\r
2458                 var args = {type: 'boolean', value: this.getValue()};\r
2459                 return args;\r
2460         },\r
2461         \r
2462     /**\r
2463      * Template method that is to validate the provided Ext.data.Record\r
2464      * against the filters configuration.\r
2465      * @param {Ext.data.Record} record The record to validate\r
2466      * @return {Boolean} true if the record is valid within the bounds\r
2467      * of the filter, false otherwise.\r
2468      */\r
2469     validateRecord : function (record) {\r
2470                 return record.get(this.dataIndex) == this.getValue();\r
2471         }\r
2472 });/** \r
2473  * @class Ext.ux.grid.filter.DateFilter\r
2474  * @extends Ext.ux.grid.filter.Filter\r
2475  * Filter by a configurable Ext.menu.DateMenu\r
2476  * <p><b><u>Example Usage:</u></b></p>\r
2477  * <pre><code>    \r
2478 var filters = new Ext.ux.grid.GridFilters({\r
2479     ...\r
2480     filters: [{\r
2481         // required configs\r
2482         type: 'date',\r
2483         dataIndex: 'dateAdded',\r
2484         \r
2485         // optional configs\r
2486         dateFormat: 'm/d/Y',  // default\r
2487         beforeText: 'Before', // default\r
2488         afterText: 'After',   // default\r
2489         onText: 'On',         // default\r
2490         pickerOpts: {\r
2491             // any DateMenu configs\r
2492         },\r
2493 \r
2494         active: true // default is false\r
2495     }]\r
2496 });\r
2497  * </code></pre>\r
2498  */\r
2499 Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2500     /**\r
2501      * @cfg {String} afterText\r
2502      * Defaults to 'After'.\r
2503      */\r
2504     afterText : 'After',\r
2505     /**\r
2506      * @cfg {String} beforeText\r
2507      * Defaults to 'Before'.\r
2508      */\r
2509     beforeText : 'Before',\r
2510     /**\r
2511      * @cfg {Object} compareMap\r
2512      * Map for assigning the comparison values used in serialization.\r
2513      */\r
2514     compareMap : {\r
2515         before: 'lt',\r
2516         after:  'gt',\r
2517         on:     'eq'\r
2518     },\r
2519     /**\r
2520      * @cfg {String} dateFormat\r
2521      * The date format to return when using getValue.\r
2522      * Defaults to 'm/d/Y'.\r
2523      */\r
2524     dateFormat : 'm/d/Y',\r
2525 \r
2526     /**\r
2527      * @cfg {Date} maxDate\r
2528      * Allowable date as passed to the Ext.DatePicker\r
2529      * Defaults to undefined.\r
2530      */\r
2531     /**\r
2532      * @cfg {Date} minDate\r
2533      * Allowable date as passed to the Ext.DatePicker\r
2534      * Defaults to undefined.\r
2535      */\r
2536     /**\r
2537      * @cfg {Array} menuItems\r
2538      * The items to be shown in this menu\r
2539      * Defaults to:<pre>\r
2540      * menuItems : ['before', 'after', '-', 'on'],\r
2541      * </pre>\r
2542      */\r
2543     menuItems : ['before', 'after', '-', 'on'],\r
2544 \r
2545     /**\r
2546      * @cfg {Object} menuItemCfgs\r
2547      * Default configuration options for each menu item\r
2548      */\r
2549     menuItemCfgs : {\r
2550         selectOnFocus: true,\r
2551         width: 125\r
2552     },\r
2553 \r
2554     /**\r
2555      * @cfg {String} onText\r
2556      * Defaults to 'On'.\r
2557      */\r
2558     onText : 'On',\r
2559     \r
2560     /**\r
2561      * @cfg {Object} pickerOpts\r
2562      * Configuration options for the date picker associated with each field.\r
2563      */\r
2564     pickerOpts : {},\r
2565 \r
2566     /**  \r
2567      * @private\r
2568      * Template method that is to initialize the filter and install required menu items.\r
2569      */\r
2570     init : function (config) {\r
2571         var menuCfg, i, len, item, cfg, Cls;\r
2572 \r
2573         menuCfg = Ext.apply(this.pickerOpts, {\r
2574             minDate: this.minDate, \r
2575             maxDate: this.maxDate, \r
2576             format:  this.dateFormat,\r
2577             listeners: {\r
2578                 scope: this,\r
2579                 select: this.onMenuSelect\r
2580             }\r
2581         });\r
2582 \r
2583         this.fields = {};\r
2584         for (i = 0, len = this.menuItems.length; i < len; i++) {\r
2585             item = this.menuItems[i];\r
2586             if (item !== '-') {\r
2587                 cfg = {\r
2588                     itemId: 'range-' + item,\r
2589                     text: this[item + 'Text'],\r
2590                     menu: new Ext.menu.DateMenu(\r
2591                         Ext.apply(menuCfg, {\r
2592                             itemId: item\r
2593                         })\r
2594                     ),\r
2595                     listeners: {\r
2596                         scope: this,\r
2597                         checkchange: this.onCheckChange\r
2598                     }\r
2599                 };\r
2600                 Cls = Ext.menu.CheckItem;\r
2601                 item = this.fields[item] = new Cls(cfg);\r
2602             }\r
2603             //this.add(item);\r
2604             this.menu.add(item);\r
2605         }\r
2606     },\r
2607 \r
2608     onCheckChange : function () {\r
2609         this.setActive(this.isActivatable());\r
2610         this.fireEvent('update', this);\r
2611     },\r
2612 \r
2613     /**  \r
2614      * @private\r
2615      * Handler method called when there is a keyup event on an input\r
2616      * item of this menu.\r
2617      */\r
2618     onInputKeyUp : function (field, e) {\r
2619         var k = e.getKey();\r
2620         if (k == e.RETURN && field.isValid()) {\r
2621             e.stopEvent();\r
2622             this.menu.hide(true);\r
2623             return;\r
2624         }\r
2625     },\r
2626 \r
2627     /**\r
2628      * Handler for when the menu for a field fires the 'select' event\r
2629      * @param {Object} date\r
2630      * @param {Object} menuItem\r
2631      * @param {Object} value\r
2632      * @param {Object} picker\r
2633      */\r
2634     onMenuSelect : function (menuItem, value, picker) {\r
2635         var fields = this.fields,\r
2636             field = this.fields[menuItem.itemId];\r
2637         \r
2638         field.setChecked(true);\r
2639         \r
2640         if (field == fields.on) {\r
2641             fields.before.setChecked(false, true);\r
2642             fields.after.setChecked(false, true);\r
2643         } else {\r
2644             fields.on.setChecked(false, true);\r
2645             if (field == fields.after && fields.before.menu.picker.value < value) {\r
2646                 fields.before.setChecked(false, true);\r
2647             } else if (field == fields.before && fields.after.menu.picker.value > value) {\r
2648                 fields.after.setChecked(false, true);\r
2649             }\r
2650         }\r
2651         this.fireEvent('update', this);\r
2652     },\r
2653 \r
2654     /**\r
2655      * @private\r
2656      * Template method that is to get and return the value of the filter.\r
2657      * @return {String} The value of this filter\r
2658      */\r
2659     getValue : function () {\r
2660         var key, result = {};\r
2661         for (key in this.fields) {\r
2662             if (this.fields[key].checked) {\r
2663                 result[key] = this.fields[key].menu.picker.getValue();\r
2664             }\r
2665         }\r
2666         return result;\r
2667     },\r
2668 \r
2669     /**\r
2670      * @private\r
2671      * Template method that is to set the value of the filter.\r
2672      * @param {Object} value The value to set the filter\r
2673      * @param {Boolean} preserve true to preserve the checked status\r
2674      * of the other fields.  Defaults to false, unchecking the\r
2675      * other fields\r
2676      */ \r
2677     setValue : function (value, preserve) {\r
2678         var key;\r
2679         for (key in this.fields) {\r
2680             if(value[key]){\r
2681                 this.fields[key].menu.picker.setValue(value[key]);\r
2682                 this.fields[key].setChecked(true);\r
2683             } else if (!preserve) {\r
2684                 this.fields[key].setChecked(false);\r
2685             }\r
2686         }\r
2687         this.fireEvent('update', this);\r
2688     },\r
2689 \r
2690     /**\r
2691      * @private\r
2692      * Template method that is to return <tt>true</tt> if the filter\r
2693      * has enough configuration information to be activated.\r
2694      * @return {Boolean}\r
2695      */\r
2696     isActivatable : function () {\r
2697         var key;\r
2698         for (key in this.fields) {\r
2699             if (this.fields[key].checked) {\r
2700                 return true;\r
2701             }\r
2702         }\r
2703         return false;\r
2704     },\r
2705 \r
2706     /**\r
2707      * @private\r
2708      * Template method that is to get and return serialized filter data for\r
2709      * transmission to the server.\r
2710      * @return {Object/Array} An object or collection of objects containing\r
2711      * key value pairs representing the current configuration of the filter.\r
2712      */\r
2713     getSerialArgs : function () {\r
2714         var args = [];\r
2715         for (var key in this.fields) {\r
2716             if(this.fields[key].checked){\r
2717                 args.push({\r
2718                     type: 'date',\r
2719                     comparison: this.compareMap[key],\r
2720                     value: this.getFieldValue(key).format(this.dateFormat)\r
2721                 });\r
2722             }\r
2723         }\r
2724         return args;\r
2725     },\r
2726 \r
2727     /**\r
2728      * Get and return the date menu picker value\r
2729      * @param {String} item The field identifier ('before', 'after', 'on')\r
2730      * @return {Date} Gets the current selected value of the date field\r
2731      */\r
2732     getFieldValue : function(item){\r
2733         return this.fields[item].menu.picker.getValue();\r
2734     },\r
2735     \r
2736     /**\r
2737      * Gets the menu picker associated with the passed field\r
2738      * @param {String} item The field identifier ('before', 'after', 'on')\r
2739      * @return {Object} The menu picker\r
2740      */\r
2741     getPicker : function(item){\r
2742         return this.fields[item].menu.picker;\r
2743     },\r
2744 \r
2745     /**\r
2746      * Template method that is to validate the provided Ext.data.Record\r
2747      * against the filters configuration.\r
2748      * @param {Ext.data.Record} record The record to validate\r
2749      * @return {Boolean} true if the record is valid within the bounds\r
2750      * of the filter, false otherwise.\r
2751      */\r
2752     validateRecord : function (record) {\r
2753         var key,\r
2754             pickerValue,\r
2755             val = record.get(this.dataIndex);\r
2756             \r
2757         if(!Ext.isDate(val)){\r
2758             return false;\r
2759         }\r
2760         val = val.clearTime(true).getTime();\r
2761         \r
2762         for (key in this.fields) {\r
2763             if (this.fields[key].checked) {\r
2764                 pickerValue = this.getFieldValue(key).clearTime(true).getTime();\r
2765                 if (key == 'before' && pickerValue <= val) {\r
2766                     return false;\r
2767                 }\r
2768                 if (key == 'after' && pickerValue >= val) {\r
2769                     return false;\r
2770                 }\r
2771                 if (key == 'on' && pickerValue != val) {\r
2772                     return false;\r
2773                 }\r
2774             }\r
2775         }\r
2776         return true;\r
2777     }\r
2778 });/** \r
2779  * @class Ext.ux.grid.filter.ListFilter\r
2780  * @extends Ext.ux.grid.filter.Filter\r
2781  * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load\r
2782  * their options the first time they are shown. ListFilter utilizes the\r
2783  * {@link Ext.ux.menu.ListMenu} component.</p>\r
2784  * <p>Although not shown here, this class accepts all configuration options\r
2785  * for {@link Ext.ux.menu.ListMenu}.</p>\r
2786  * \r
2787  * <p><b><u>Example Usage:</u></b></p>\r
2788  * <pre><code>    \r
2789 var filters = new Ext.ux.grid.GridFilters({\r
2790     ...\r
2791     filters: [{\r
2792         type: 'list',\r
2793         dataIndex: 'size',\r
2794         phpMode: true,\r
2795         // options will be used as data to implicitly creates an ArrayStore\r
2796         options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
2797     }]\r
2798 });\r
2799  * </code></pre>\r
2800  * \r
2801  */\r
2802 Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2803 \r
2804     /**\r
2805      * @cfg {Array} options\r
2806      * <p><code>data</code> to be used to implicitly create a data store\r
2807      * to back this list when the data source is <b>local</b>. If the\r
2808      * data for the list is remote, use the <code>{@link #store}</code>\r
2809      * config instead.</p>\r
2810      * <br><p>Each item within the provided array may be in one of the\r
2811      * following formats:</p>\r
2812      * <div class="mdetail-params"><ul>\r
2813      * <li><b>Array</b> :\r
2814      * <pre><code>\r
2815 options: [\r
2816     [11, 'extra small'], \r
2817     [18, 'small'],\r
2818     [22, 'medium'],\r
2819     [35, 'large'],\r
2820     [44, 'extra large']\r
2821 ]\r
2822      * </code></pre>\r
2823      * </li>\r
2824      * <li><b>Object</b> :\r
2825      * <pre><code>\r
2826 labelField: 'name', // override default of 'text'\r
2827 options: [\r
2828     {id: 11, name:'extra small'}, \r
2829     {id: 18, name:'small'}, \r
2830     {id: 22, name:'medium'}, \r
2831     {id: 35, name:'large'}, \r
2832     {id: 44, name:'extra large'} \r
2833 ]\r
2834      * </code></pre>\r
2835      * </li>\r
2836      * <li><b>String</b> :\r
2837      * <pre><code>\r
2838      * options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
2839      * </code></pre>\r
2840      * </li>\r
2841      */\r
2842     /**\r
2843      * @cfg {Boolean} phpMode\r
2844      * <p>Adjust the format of this filter. Defaults to false.</p>\r
2845      * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>\r
2846      * <pre><code>\r
2847 // phpMode == false (default):\r
2848 filter[0][data][type] list\r
2849 filter[0][data][value] value1\r
2850 filter[0][data][value] value2\r
2851 filter[0][field] prod \r
2852 \r
2853 // phpMode == true:\r
2854 filter[0][data][type] list\r
2855 filter[0][data][value] value1, value2\r
2856 filter[0][field] prod \r
2857      * </code></pre>\r
2858      * When GridFilters <code>@cfg encode = true</code>:\r
2859      * <pre><code>\r
2860 // phpMode == false (default):\r
2861 filter : [{"type":"list","value":["small","medium"],"field":"size"}]\r
2862 \r
2863 // phpMode == true:\r
2864 filter : [{"type":"list","value":"small,medium","field":"size"}]\r
2865      * </code></pre>\r
2866      */\r
2867     phpMode : false,\r
2868     /**\r
2869      * @cfg {Ext.data.Store} store\r
2870      * The {@link Ext.data.Store} this list should use as its data source\r
2871      * when the data source is <b>remote</b>. If the data for the list\r
2872      * is local, use the <code>{@link #options}</code> config instead.\r
2873      */\r
2874 \r
2875     /**  \r
2876      * @private\r
2877      * Template method that is to initialize the filter and install required menu items.\r
2878      * @param {Object} config\r
2879      */\r
2880     init : function (config) {\r
2881         this.dt = new Ext.util.DelayedTask(this.fireUpdate, this);\r
2882 \r
2883         // if a menu already existed, do clean up first\r
2884         if (this.menu){\r
2885             this.menu.destroy();\r
2886         }\r
2887         this.menu = new Ext.ux.menu.ListMenu(config);\r
2888         this.menu.on('checkchange', this.onCheckChange, this);\r
2889     },\r
2890     \r
2891     /**\r
2892      * @private\r
2893      * Template method that is to get and return the value of the filter.\r
2894      * @return {String} The value of this filter\r
2895      */\r
2896     getValue : function () {\r
2897         return this.menu.getSelected();\r
2898     },\r
2899     /**\r
2900      * @private\r
2901      * Template method that is to set the value of the filter.\r
2902      * @param {Object} value The value to set the filter\r
2903      */ \r
2904     setValue : function (value) {\r
2905         this.menu.setSelected(value);\r
2906         this.fireEvent('update', this);\r
2907     },\r
2908 \r
2909     /**\r
2910      * @private\r
2911      * Template method that is to return <tt>true</tt> if the filter\r
2912      * has enough configuration information to be activated.\r
2913      * @return {Boolean}\r
2914      */\r
2915     isActivatable : function () {\r
2916         return this.getValue().length > 0;\r
2917     },\r
2918     \r
2919     /**\r
2920      * @private\r
2921      * Template method that is to get and return serialized filter data for\r
2922      * transmission to the server.\r
2923      * @return {Object/Array} An object or collection of objects containing\r
2924      * key value pairs representing the current configuration of the filter.\r
2925      */\r
2926     getSerialArgs : function () {\r
2927         var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()};\r
2928         return args;\r
2929     },\r
2930 \r
2931     /** @private */\r
2932     onCheckChange : function(){\r
2933         this.dt.delay(this.updateBuffer);\r
2934     },\r
2935     \r
2936     \r
2937     /**\r
2938      * Template method that is to validate the provided Ext.data.Record\r
2939      * against the filters configuration.\r
2940      * @param {Ext.data.Record} record The record to validate\r
2941      * @return {Boolean} true if the record is valid within the bounds\r
2942      * of the filter, false otherwise.\r
2943      */\r
2944     validateRecord : function (record) {\r
2945         return this.getValue().indexOf(record.get(this.dataIndex)) > -1;\r
2946     }\r
2947 });/** \r
2948  * @class Ext.ux.grid.filter.NumericFilter\r
2949  * @extends Ext.ux.grid.filter.Filter\r
2950  * Filters using an Ext.ux.menu.RangeMenu.\r
2951  * <p><b><u>Example Usage:</u></b></p>\r
2952  * <pre><code>    \r
2953 var filters = new Ext.ux.grid.GridFilters({\r
2954     ...\r
2955     filters: [{\r
2956         type: 'numeric',\r
2957         dataIndex: 'price'\r
2958     }]\r
2959 });\r
2960  * </code></pre> \r
2961  */\r
2962 Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2963 \r
2964     /**\r
2965      * @cfg {Object} fieldCls\r
2966      * The Class to use to construct each field item within this menu\r
2967      * Defaults to:<pre>\r
2968      * fieldCls : Ext.form.NumberField\r
2969      * </pre>\r
2970      */\r
2971     fieldCls : Ext.form.NumberField,\r
2972     /**\r
2973      * @cfg {Object} fieldCfg\r
2974      * The default configuration options for any field item unless superseded\r
2975      * by the <code>{@link #fields}</code> configuration.\r
2976      * Defaults to:<pre>\r
2977      * fieldCfg : {}\r
2978      * </pre>\r
2979      * Example usage:\r
2980      * <pre><code>\r
2981 fieldCfg : {\r
2982     width: 150,\r
2983 },\r
2984      * </code></pre>\r
2985      */\r
2986     /**\r
2987      * @cfg {Object} fields\r
2988      * The field items may be configured individually\r
2989      * Defaults to <tt>undefined</tt>.\r
2990      * Example usage:\r
2991      * <pre><code>\r
2992 fields : {\r
2993     gt: { // override fieldCfg options\r
2994         width: 200,\r
2995         fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}\r
2996     }\r
2997 },\r
2998      * </code></pre>\r
2999      */\r
3000     /**\r
3001      * @cfg {Object} iconCls\r
3002      * The iconCls to be applied to each comparator field item.\r
3003      * Defaults to:<pre>\r
3004 iconCls : {\r
3005     gt : 'ux-rangemenu-gt',\r
3006     lt : 'ux-rangemenu-lt',\r
3007     eq : 'ux-rangemenu-eq'\r
3008 }\r
3009      * </pre>\r
3010      */\r
3011     iconCls : {\r
3012         gt : 'ux-rangemenu-gt',\r
3013         lt : 'ux-rangemenu-lt',\r
3014         eq : 'ux-rangemenu-eq'\r
3015     },\r
3016 \r
3017     /**\r
3018      * @cfg {Object} menuItemCfgs\r
3019      * Default configuration options for each menu item\r
3020      * Defaults to:<pre>\r
3021 menuItemCfgs : {\r
3022     emptyText: 'Enter Filter Text...',\r
3023     selectOnFocus: true,\r
3024     width: 125\r
3025 }\r
3026      * </pre>\r
3027      */\r
3028     menuItemCfgs : {\r
3029         emptyText: 'Enter Filter Text...',\r
3030         selectOnFocus: true,\r
3031         width: 125\r
3032     },\r
3033 \r
3034     /**\r
3035      * @cfg {Array} menuItems\r
3036      * The items to be shown in this menu.  Items are added to the menu\r
3037      * according to their position within this array. Defaults to:<pre>\r
3038      * menuItems : ['lt','gt','-','eq']\r
3039      * </pre>\r
3040      */\r
3041     menuItems : ['lt', 'gt', '-', 'eq'],\r
3042 \r
3043     /**  \r
3044      * @private\r
3045      * Template method that is to initialize the filter and install required menu items.\r
3046      */\r
3047     init : function (config) {\r
3048         // if a menu already existed, do clean up first\r
3049         if (this.menu){\r
3050             this.menu.destroy();\r
3051         }        \r
3052         this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, {\r
3053             // pass along filter configs to the menu\r
3054             fieldCfg : this.fieldCfg || {},\r
3055             fieldCls : this.fieldCls,\r
3056             fields : this.fields || {},\r
3057             iconCls: this.iconCls,\r
3058             menuItemCfgs: this.menuItemCfgs,\r
3059             menuItems: this.menuItems,\r
3060             updateBuffer: this.updateBuffer\r
3061         }));\r
3062         // relay the event fired by the menu\r
3063         this.menu.on('update', this.fireUpdate, this);\r
3064     },\r
3065     \r
3066     /**\r
3067      * @private\r
3068      * Template method that is to get and return the value of the filter.\r
3069      * @return {String} The value of this filter\r
3070      */\r
3071     getValue : function () {\r
3072         return this.menu.getValue();\r
3073     },\r
3074 \r
3075     /**\r
3076      * @private\r
3077      * Template method that is to set the value of the filter.\r
3078      * @param {Object} value The value to set the filter\r
3079      */ \r
3080     setValue : function (value) {\r
3081         this.menu.setValue(value);\r
3082     },\r
3083 \r
3084     /**\r
3085      * @private\r
3086      * Template method that is to return <tt>true</tt> if the filter\r
3087      * has enough configuration information to be activated.\r
3088      * @return {Boolean}\r
3089      */\r
3090     isActivatable : function () {\r
3091         var values = this.getValue();\r
3092         for (key in values) {\r
3093             if (values[key] !== undefined) {\r
3094                 return true;\r
3095             }\r
3096         }\r
3097         return false;\r
3098     },\r
3099     \r
3100     /**\r
3101      * @private\r
3102      * Template method that is to get and return serialized filter data for\r
3103      * transmission to the server.\r
3104      * @return {Object/Array} An object or collection of objects containing\r
3105      * key value pairs representing the current configuration of the filter.\r
3106      */\r
3107     getSerialArgs : function () {\r
3108         var key,\r
3109             args = [],\r
3110             values = this.menu.getValue();\r
3111         for (key in values) {\r
3112             args.push({\r
3113                 type: 'numeric',\r
3114                 comparison: key,\r
3115                 value: values[key]\r
3116             });\r
3117         }\r
3118         return args;\r
3119     },\r
3120 \r
3121     /**\r
3122      * Template method that is to validate the provided Ext.data.Record\r
3123      * against the filters configuration.\r
3124      * @param {Ext.data.Record} record The record to validate\r
3125      * @return {Boolean} true if the record is valid within the bounds\r
3126      * of the filter, false otherwise.\r
3127      */\r
3128     validateRecord : function (record) {\r
3129         var val = record.get(this.dataIndex),\r
3130             values = this.getValue();\r
3131         if (values.eq !== undefined && val != values.eq) {\r
3132             return false;\r
3133         }\r
3134         if (values.lt !== undefined && val >= values.lt) {\r
3135             return false;\r
3136         }\r
3137         if (values.gt !== undefined && val <= values.gt) {\r
3138             return false;\r
3139         }\r
3140         return true;\r
3141     }\r
3142 });/** \r
3143  * @class Ext.ux.grid.filter.StringFilter\r
3144  * @extends Ext.ux.grid.filter.Filter\r
3145  * Filter by a configurable Ext.form.TextField\r
3146  * <p><b><u>Example Usage:</u></b></p>\r
3147  * <pre><code>    \r
3148 var filters = new Ext.ux.grid.GridFilters({\r
3149     ...\r
3150     filters: [{\r
3151         // required configs\r
3152         type: 'string',\r
3153         dataIndex: 'name',\r
3154         \r
3155         // optional configs\r
3156         value: 'foo',\r
3157         active: true, // default is false\r
3158         iconCls: 'ux-gridfilter-text-icon' // default\r
3159         // any Ext.form.TextField configs accepted\r
3160     }]\r
3161 });\r
3162  * </code></pre>\r
3163  */\r
3164 Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
3165 \r
3166     /**\r
3167      * @cfg {String} iconCls\r
3168      * The iconCls to be applied to the menu item.\r
3169      * Defaults to <tt>'ux-gridfilter-text-icon'</tt>.\r
3170      */\r
3171     iconCls : 'ux-gridfilter-text-icon',\r
3172 \r
3173     emptyText: 'Enter Filter Text...',\r
3174     selectOnFocus: true,\r
3175     width: 125,\r
3176     \r
3177     /**  \r
3178      * @private\r
3179      * Template method that is to initialize the filter and install required menu items.\r
3180      */\r
3181     init : function (config) {\r
3182         Ext.applyIf(config, {\r
3183             enableKeyEvents: true,\r
3184             iconCls: this.iconCls,\r
3185             listeners: {\r
3186                 scope: this,\r
3187                 keyup: this.onInputKeyUp\r
3188             }\r
3189         });\r
3190 \r
3191         this.inputItem = new Ext.form.TextField(config); \r
3192         this.menu.add(this.inputItem);\r
3193         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
3194     },\r
3195     \r
3196     /**\r
3197      * @private\r
3198      * Template method that is to get and return the value of the filter.\r
3199      * @return {String} The value of this filter\r
3200      */\r
3201     getValue : function () {\r
3202         return this.inputItem.getValue();\r
3203     },\r
3204     \r
3205     /**\r
3206      * @private\r
3207      * Template method that is to set the value of the filter.\r
3208      * @param {Object} value The value to set the filter\r
3209      */ \r
3210     setValue : function (value) {\r
3211         this.inputItem.setValue(value);\r
3212         this.fireEvent('update', this);\r
3213     },\r
3214 \r
3215     /**\r
3216      * @private\r
3217      * Template method that is to return <tt>true</tt> if the filter\r
3218      * has enough configuration information to be activated.\r
3219      * @return {Boolean}\r
3220      */\r
3221     isActivatable : function () {\r
3222         return this.inputItem.getValue().length > 0;\r
3223     },\r
3224 \r
3225     /**\r
3226      * @private\r
3227      * Template method that is to get and return serialized filter data for\r
3228      * transmission to the server.\r
3229      * @return {Object/Array} An object or collection of objects containing\r
3230      * key value pairs representing the current configuration of the filter.\r
3231      */\r
3232     getSerialArgs : function () {\r
3233         return {type: 'string', value: this.getValue()};\r
3234     },\r
3235 \r
3236     /**\r
3237      * Template method that is to validate the provided Ext.data.Record\r
3238      * against the filters configuration.\r
3239      * @param {Ext.data.Record} record The record to validate\r
3240      * @return {Boolean} true if the record is valid within the bounds\r
3241      * of the filter, false otherwise.\r
3242      */\r
3243     validateRecord : function (record) {\r
3244         var val = record.get(this.dataIndex);\r
3245 \r
3246         if(typeof val != 'string') {\r
3247             return (this.getValue().length === 0);\r
3248         }\r
3249 \r
3250         return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;\r
3251     },\r
3252     \r
3253     /**  \r
3254      * @private\r
3255      * Handler method called when there is a keyup event on this.inputItem\r
3256      */\r
3257     onInputKeyUp : function (field, e) {\r
3258         var k = e.getKey();\r
3259         if (k == e.RETURN && field.isValid()) {\r
3260             e.stopEvent();\r
3261             this.menu.hide(true);\r
3262             return;\r
3263         }\r
3264         // restart the timer\r
3265         this.updateTask.delay(this.updateBuffer);\r
3266     }\r
3267 });\r
3268 Ext.namespace('Ext.ux.menu');\r
3269 \r
3270 /** \r
3271  * @class Ext.ux.menu.ListMenu\r
3272  * @extends Ext.menu.Menu\r
3273  * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.\r
3274  * Although not listed as configuration options for this class, this class\r
3275  * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.\r
3276  */\r
3277 Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {\r
3278     /**\r
3279      * @cfg {String} labelField\r
3280      * Defaults to 'text'.\r
3281      */\r
3282     labelField :  'text',\r
3283     /**\r
3284      * @cfg {String} paramPrefix\r
3285      * Defaults to 'Loading...'.\r
3286      */\r
3287     loadingText : 'Loading...',\r
3288     /**\r
3289      * @cfg {Boolean} loadOnShow\r
3290      * Defaults to true.\r
3291      */\r
3292     loadOnShow : true,\r
3293     /**\r
3294      * @cfg {Boolean} single\r
3295      * Specify true to group all items in this list into a single-select\r
3296      * radio button group. Defaults to false.\r
3297      */\r
3298     single : false,\r
3299 \r
3300     constructor : function (cfg) {\r
3301         this.selected = [];\r
3302         this.addEvents(\r
3303             /**\r
3304              * @event checkchange\r
3305              * Fires when there is a change in checked items from this list\r
3306              * @param {Object} item Ext.menu.CheckItem\r
3307              * @param {Object} checked The checked value that was set\r
3308              */\r
3309             'checkchange'\r
3310         );\r
3311       \r
3312         Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {});\r
3313     \r
3314         if(!cfg.store && cfg.options){\r
3315             var options = [];\r
3316             for(var i=0, len=cfg.options.length; i<len; i++){\r
3317                 var value = cfg.options[i];\r
3318                 switch(Ext.type(value)){\r
3319                     case 'array':  options.push(value); break;\r
3320                     case 'object': options.push([value.id, value[this.labelField]]); break;\r
3321                     case 'string': options.push([value, value]); break;\r
3322                 }\r
3323             }\r
3324             \r
3325             this.store = new Ext.data.Store({\r
3326                 reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField]),\r
3327                 data:   options,\r
3328                 listeners: {\r
3329                     'load': this.onLoad,\r
3330                     scope:  this\r
3331                 }\r
3332             });\r
3333             this.loaded = true;\r
3334         } else {\r
3335             this.add({text: this.loadingText, iconCls: 'loading-indicator'});\r
3336             this.store.on('load', this.onLoad, this);\r
3337         }\r
3338     },\r
3339 \r
3340     destroy : function () {\r
3341         if (this.store) {\r
3342             this.store.destroy();    \r
3343         }\r
3344         Ext.ux.menu.ListMenu.superclass.destroy.call(this);\r
3345     },\r
3346 \r
3347     /**\r
3348      * Lists will initially show a 'loading' item while the data is retrieved from the store.\r
3349      * In some cases the loaded data will result in a list that goes off the screen to the\r
3350      * right (as placement calculations were done with the loading item). This adapter will\r
3351      * allow show to be called with no arguments to show with the previous arguments and\r
3352      * thus recalculate the width and potentially hang the menu from the left.\r
3353      */\r
3354     show : function () {\r
3355         var lastArgs = null;\r
3356         return function(){\r
3357             if(arguments.length === 0){\r
3358                 Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs);\r
3359             } else {\r
3360                 lastArgs = arguments;\r
3361                 if (this.loadOnShow && !this.loaded) {\r
3362                     this.store.load();\r
3363                 }\r
3364                 Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments);\r
3365             }\r
3366         };\r
3367     }(),\r
3368     \r
3369     /** @private */\r
3370     onLoad : function (store, records) {\r
3371         var visible = this.isVisible();\r
3372         this.hide(false);\r
3373         \r
3374         this.removeAll(true);\r
3375         \r
3376         var gid = this.single ? Ext.id() : null;\r
3377         for(var i=0, len=records.length; i<len; i++){\r
3378             var item = new Ext.menu.CheckItem({\r
3379                 text:    records[i].get(this.labelField), \r
3380                 group:   gid,\r
3381                 checked: this.selected.indexOf(records[i].id) > -1,\r
3382                 hideOnClick: false});\r
3383             \r
3384             item.itemId = records[i].id;\r
3385             item.on('checkchange', this.checkChange, this);\r
3386                         \r
3387             this.add(item);\r
3388         }\r
3389         \r
3390         this.loaded = true;\r
3391         \r
3392         if (visible) {\r
3393             this.show();\r
3394         }       \r
3395         this.fireEvent('load', this, records);\r
3396     },\r
3397 \r
3398     /**\r
3399      * Get the selected items.\r
3400      * @return {Array} selected\r
3401      */\r
3402     getSelected : function () {\r
3403         return this.selected;\r
3404     },\r
3405     \r
3406     /** @private */\r
3407     setSelected : function (value) {\r
3408         value = this.selected = [].concat(value);\r
3409 \r
3410         if (this.loaded) {\r
3411             this.items.each(function(item){\r
3412                 item.setChecked(false, true);\r
3413                 for (var i = 0, len = value.length; i < len; i++) {\r
3414                     if (item.itemId == value[i]) {\r
3415                         item.setChecked(true, true);\r
3416                     }\r
3417                 }\r
3418             }, this);\r
3419         }\r
3420     },\r
3421     \r
3422     /**\r
3423      * Handler for the 'checkchange' event from an check item in this menu\r
3424      * @param {Object} item Ext.menu.CheckItem\r
3425      * @param {Object} checked The checked value that was set\r
3426      */\r
3427     checkChange : function (item, checked) {\r
3428         var value = [];\r
3429         this.items.each(function(item){\r
3430             if (item.checked) {\r
3431                 value.push(item.itemId);\r
3432             }\r
3433         },this);\r
3434         this.selected = value;\r
3435         \r
3436         this.fireEvent('checkchange', item, checked);\r
3437     }    \r
3438 });Ext.ns('Ext.ux.menu');\r
3439 \r
3440 /** \r
3441  * @class Ext.ux.menu.RangeMenu\r
3442  * @extends Ext.menu.Menu\r
3443  * Custom implementation of Ext.menu.Menu that has preconfigured\r
3444  * items for gt, lt, eq.\r
3445  * <p><b><u>Example Usage:</u></b></p>\r
3446  * <pre><code>    \r
3447 \r
3448  * </code></pre> \r
3449  */\r
3450 Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, {\r
3451 \r
3452     constructor : function (config) {\r
3453 \r
3454         Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config);\r
3455 \r
3456         this.addEvents(\r
3457             /**\r
3458              * @event update\r
3459              * Fires when a filter configuration has changed\r
3460              * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
3461              */\r
3462             'update'\r
3463         );\r
3464       \r
3465         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
3466     \r
3467         var i, len, item, cfg, Cls;\r
3468 \r
3469         for (i = 0, len = this.menuItems.length; i < len; i++) {\r
3470             item = this.menuItems[i];\r
3471             if (item !== '-') {\r
3472                 // defaults\r
3473                 cfg = {\r
3474                     itemId: 'range-' + item,\r
3475                     enableKeyEvents: true,\r
3476                     iconCls: this.iconCls[item] || 'no-icon',\r
3477                     listeners: {\r
3478                         scope: this,\r
3479                         keyup: this.onInputKeyUp\r
3480                     }\r
3481                 };\r
3482                 Ext.apply(\r
3483                     cfg,\r
3484                     // custom configs\r
3485                     Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]),\r
3486                     // configurable defaults\r
3487                     this.menuItemCfgs\r
3488                 );\r
3489                 Cls = cfg.fieldCls || this.fieldCls;\r
3490                 item = this.fields[item] = new Cls(cfg);\r
3491             }\r
3492             this.add(item);\r
3493         }\r
3494     },\r
3495 \r
3496     /**\r
3497      * @private\r
3498      * called by this.updateTask\r
3499      */\r
3500     fireUpdate : function () {\r
3501         this.fireEvent('update', this);\r
3502     },\r
3503     \r
3504     /**\r
3505      * Get and return the value of the filter.\r
3506      * @return {String} The value of this filter\r
3507      */\r
3508     getValue : function () {\r
3509         var result = {}, key, field;\r
3510         for (key in this.fields) {\r
3511             field = this.fields[key];\r
3512             if (field.isValid() && String(field.getValue()).length > 0) {\r
3513                 result[key] = field.getValue();\r
3514             }\r
3515         }\r
3516         return result;\r
3517     },\r
3518   \r
3519     /**\r
3520      * Set the value of this menu and fires the 'update' event.\r
3521      * @param {Object} data The data to assign to this menu\r
3522      */ \r
3523     setValue : function (data) {\r
3524         var key;\r
3525         for (key in this.fields) {\r
3526             this.fields[key].setValue(data[key] !== undefined ? data[key] : '');\r
3527         }\r
3528         this.fireEvent('update', this);\r
3529     },\r
3530 \r
3531     /**  \r
3532      * @private\r
3533      * Handler method called when there is a keyup event on an input\r
3534      * item of this menu.\r
3535      */\r
3536     onInputKeyUp : function (field, e) {\r
3537         var k = e.getKey();\r
3538         if (k == e.RETURN && field.isValid()) {\r
3539             e.stopEvent();\r
3540             this.hide(true);\r
3541             return;\r
3542         }\r
3543         \r
3544         if (field == this.fields.eq) {\r
3545             if (this.fields.gt) {\r
3546                 this.fields.gt.setValue(null);\r
3547             }\r
3548             if (this.fields.lt) {\r
3549                 this.fields.lt.setValue(null);\r
3550             }\r
3551         }\r
3552         else {\r
3553             this.fields.eq.setValue(null);\r
3554         }\r
3555         \r
3556         // restart the timer\r
3557         this.updateTask.delay(this.updateBuffer);\r
3558     }\r
3559 });\r
3560 Ext.ns('Ext.ux.grid');\r
3561 \r
3562 /**\r
3563  * @class Ext.ux.grid.GroupSummary\r
3564  * @extends Ext.util.Observable\r
3565  * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
3566  * updated grouped summary row.\r
3567  */\r
3568 Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
3569     /**\r
3570      * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
3571 summaryRenderer: function(v, params, data){\r
3572     return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
3573 },\r
3574      * </code></pre>\r
3575      */\r
3576     /**\r
3577      * @cfg {String} summaryType (Optional) The type of\r
3578      * calculation to be used for the column.  For options available see\r
3579      * {@link #Calculations}.\r
3580      */\r
3581 \r
3582     constructor : function(config){\r
3583         Ext.apply(this, config);\r
3584         Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
3585     },\r
3586     init : function(grid){\r
3587         this.grid = grid;\r
3588         var v = this.view = grid.getView();\r
3589         v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
3590 \r
3591         v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
3592         v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
3593         v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
3594         v.afterMethod('onUpdate', this.doUpdate, this);\r
3595         v.afterMethod('onRemove', this.doRemove, this);\r
3596 \r
3597         if(!this.rowTpl){\r
3598             this.rowTpl = new Ext.Template(\r
3599                 '<div class="x-grid3-summary-row" style="{tstyle}">',\r
3600                 '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
3601                     '<tbody><tr>{cells}</tr></tbody>',\r
3602                 '</table></div>'\r
3603             );\r
3604             this.rowTpl.disableFormats = true;\r
3605         }\r
3606         this.rowTpl.compile();\r
3607 \r
3608         if(!this.cellTpl){\r
3609             this.cellTpl = new Ext.Template(\r
3610                 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
3611                 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
3612                 "</td>"\r
3613             );\r
3614             this.cellTpl.disableFormats = true;\r
3615         }\r
3616         this.cellTpl.compile();\r
3617     },\r
3618 \r
3619     /**\r
3620      * Toggle the display of the summary row on/off\r
3621      * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
3622      */\r
3623     toggleSummaries : function(visible){\r
3624         var el = this.grid.getGridEl();\r
3625         if(el){\r
3626             if(visible === undefined){\r
3627                 visible = el.hasClass('x-grid-hide-summary');\r
3628             }\r
3629             el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
3630         }\r
3631     },\r
3632 \r
3633     renderSummary : function(o, cs){\r
3634         cs = cs || this.view.getColumnData();\r
3635         var cfg = this.grid.getColumnModel().config,\r
3636             buf = [], c, p = {}, cf, last = cs.length-1;\r
3637         for(var i = 0, len = cs.length; i < len; i++){\r
3638             c = cs[i];\r
3639             cf = cfg[i];\r
3640             p.id = c.id;\r
3641             p.style = c.style;\r
3642             p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
3643             if(cf.summaryType || cf.summaryRenderer){\r
3644                 p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
3645             }else{\r
3646                 p.value = '';\r
3647             }\r
3648             if(p.value == undefined || p.value === "") p.value = "&#160;";\r
3649             buf[buf.length] = this.cellTpl.apply(p);\r
3650         }\r
3651 \r
3652         return this.rowTpl.apply({\r
3653             tstyle: 'width:'+this.view.getTotalWidth()+';',\r
3654             cells: buf.join('')\r
3655         });\r
3656     },\r
3657 \r
3658     /**\r
3659      * @private\r
3660      * @param {Object} rs\r
3661      * @param {Object} cs\r
3662      */\r
3663     calculate : function(rs, cs){\r
3664         var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;\r
3665         for(var j = 0, jlen = rs.length; j < jlen; j++){\r
3666             r = rs[j];\r
3667             for(var i = 0, len = cs.length; i < len; i++){\r
3668                 c = cs[i];\r
3669                 cf = cfg[i];\r
3670                 if(cf.summaryType){\r
3671                     data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
3672                 }\r
3673             }\r
3674         }\r
3675         return data;\r
3676     },\r
3677 \r
3678     doGroupEnd : function(buf, g, cs, ds, colCount){\r
3679         var data = this.calculate(g.rs, cs);\r
3680         buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
3681     },\r
3682 \r
3683     doWidth : function(col, w, tw){\r
3684         var gs = this.view.getGroups(), s;\r
3685         for(var i = 0, len = gs.length; i < len; i++){\r
3686             s = gs[i].childNodes[2];\r
3687             s.style.width = tw;\r
3688             s.firstChild.style.width = tw;\r
3689             s.firstChild.rows[0].childNodes[col].style.width = w;\r
3690         }\r
3691     },\r
3692 \r
3693     doAllWidths : function(ws, tw){\r
3694         var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
3695         for(var i = 0, len = gs.length; i < len; i++){\r
3696             s = gs[i].childNodes[2];\r
3697             s.style.width = tw;\r
3698             s.firstChild.style.width = tw;\r
3699             cells = s.firstChild.rows[0].childNodes;\r
3700             for(var j = 0; j < wlen; j++){\r
3701                 cells[j].style.width = ws[j];\r
3702             }\r
3703         }\r
3704     },\r
3705 \r
3706     doHidden : function(col, hidden, tw){\r
3707         var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
3708         for(var i = 0, len = gs.length; i < len; i++){\r
3709             s = gs[i].childNodes[2];\r
3710             s.style.width = tw;\r
3711             s.firstChild.style.width = tw;\r
3712             s.firstChild.rows[0].childNodes[col].style.display = display;\r
3713         }\r
3714     },\r
3715 \r
3716     // Note: requires that all (or the first) record in the\r
3717     // group share the same group value. Returns false if the group\r
3718     // could not be found.\r
3719     refreshSummary : function(groupValue){\r
3720         return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
3721     },\r
3722 \r
3723     getSummaryNode : function(gid){\r
3724         var g = Ext.fly(gid, '_gsummary');\r
3725         if(g){\r
3726             return g.down('.x-grid3-summary-row', true);\r
3727         }\r
3728         return null;\r
3729     },\r
3730 \r
3731     refreshSummaryById : function(gid){\r
3732         var g = Ext.getDom(gid);\r
3733         if(!g){\r
3734             return false;\r
3735         }\r
3736         var rs = [];\r
3737         this.grid.getStore().each(function(r){\r
3738             if(r._groupId == gid){\r
3739                 rs[rs.length] = r;\r
3740             }\r
3741         });\r
3742         var cs = this.view.getColumnData(),\r
3743             data = this.calculate(rs, cs),\r
3744             markup = this.renderSummary({data: data}, cs),\r
3745             existing = this.getSummaryNode(gid);\r
3746             \r
3747         if(existing){\r
3748             g.removeChild(existing);\r
3749         }\r
3750         Ext.DomHelper.append(g, markup);\r
3751         return true;\r
3752     },\r
3753 \r
3754     doUpdate : function(ds, record){\r
3755         this.refreshSummaryById(record._groupId);\r
3756     },\r
3757 \r
3758     doRemove : function(ds, record, index, isUpdate){\r
3759         if(!isUpdate){\r
3760             this.refreshSummaryById(record._groupId);\r
3761         }\r
3762     },\r
3763 \r
3764     /**\r
3765      * Show a message in the summary row.\r
3766      * <pre><code>\r
3767 grid.on('afteredit', function(){\r
3768     var groupValue = 'Ext Forms: Field Anchoring';\r
3769     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
3770 });\r
3771      * </code></pre>\r
3772      * @param {String} groupValue\r
3773      * @param {String} msg Text to use as innerHTML for the summary row.\r
3774      */\r
3775     showSummaryMsg : function(groupValue, msg){\r
3776         var gid = this.view.getGroupId(groupValue),\r
3777              node = this.getSummaryNode(gid);\r
3778         if(node){\r
3779             node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
3780         }\r
3781     }\r
3782 });\r
3783 \r
3784 //backwards compat\r
3785 Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
3786 \r
3787 \r
3788 /**\r
3789  * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
3790  * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
3791  * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
3792  * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
3793  * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
3794  * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
3795  * </ul></div>\r
3796  * <p>Custom calculations may be implemented.  An example of\r
3797  * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
3798 // define a custom summary function\r
3799 Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
3800     return v + (record.data.estimate * record.data.rate);\r
3801 };\r
3802  * </code></pre>\r
3803  * @property Calculations\r
3804  */\r
3805 \r
3806 Ext.ux.grid.GroupSummary.Calculations = {\r
3807     'sum' : function(v, record, field){\r
3808         return v + (record.data[field]||0);\r
3809     },\r
3810 \r
3811     'count' : function(v, record, field, data){\r
3812         return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
3813     },\r
3814 \r
3815     'max' : function(v, record, field, data){\r
3816         var v = record.data[field];\r
3817         var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
3818         return v > max ? (data[field+'max'] = v) : max;\r
3819     },\r
3820 \r
3821     'min' : function(v, record, field, data){\r
3822         var v = record.data[field];\r
3823         var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
3824         return v < min ? (data[field+'min'] = v) : min;\r
3825     },\r
3826 \r
3827     'average' : function(v, record, field, data){\r
3828         var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
3829         var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
3830         return t === 0 ? 0 : t / c;\r
3831     }\r
3832 };\r
3833 Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
3834 \r
3835 /**\r
3836  * @class Ext.ux.grid.HybridSummary\r
3837  * @extends Ext.ux.grid.GroupSummary\r
3838  * Adds capability to specify the summary data for the group via json as illustrated here:\r
3839  * <pre><code>\r
3840 {\r
3841     data: [\r
3842         {\r
3843             projectId: 100,     project: 'House',\r
3844             taskId:    112, description: 'Paint',\r
3845             estimate:    6,        rate:     150,\r
3846             due:'06/24/2007'\r
3847         },\r
3848         ...\r
3849     ],\r
3850 \r
3851     summaryData: {\r
3852         'House': {\r
3853             description: 14, estimate: 9,\r
3854                    rate: 99, due: new Date(2009, 6, 29),\r
3855                    cost: 999\r
3856         }\r
3857     }\r
3858 }\r
3859  * </code></pre>\r
3860  *\r
3861  */\r
3862 Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {\r
3863     /**\r
3864      * @private\r
3865      * @param {Object} rs\r
3866      * @param {Object} cs\r
3867      */\r
3868     calculate : function(rs, cs){\r
3869         var gcol = this.view.getGroupField(),\r
3870             gvalue = rs[0].data[gcol],\r
3871             gdata = this.getSummaryData(gvalue);\r
3872         return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);\r
3873     },\r
3874 \r
3875     /**\r
3876      * <pre><code>\r
3877 grid.on('afteredit', function(){\r
3878     var groupValue = 'Ext Forms: Field Anchoring';\r
3879     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
3880     setTimeout(function(){ // simulate server call\r
3881         // HybridSummary class implements updateSummaryData\r
3882         summary.updateSummaryData(groupValue,\r
3883             // create data object based on configured dataIndex\r
3884             {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});\r
3885     }, 2000);\r
3886 });\r
3887      * </code></pre>\r
3888      * @param {String} groupValue\r
3889      * @param {Object} data data object\r
3890      * @param {Boolean} skipRefresh (Optional) Defaults to false\r
3891      */\r
3892     updateSummaryData : function(groupValue, data, skipRefresh){\r
3893         var json = this.grid.getStore().reader.jsonData;\r
3894         if(!json.summaryData){\r
3895             json.summaryData = {};\r
3896         }\r
3897         json.summaryData[groupValue] = data;\r
3898         if(!skipRefresh){\r
3899             this.refreshSummary(groupValue);\r
3900         }\r
3901     },\r
3902 \r
3903     /**\r
3904      * Returns the summaryData for the specified groupValue or null.\r
3905      * @param {String} groupValue\r
3906      * @return {Object} summaryData\r
3907      */\r
3908     getSummaryData : function(groupValue){\r
3909         var json = this.grid.getStore().reader.jsonData;\r
3910         if(json && json.summaryData){\r
3911             return json.summaryData[groupValue];\r
3912         }\r
3913         return null;\r
3914     }\r
3915 });\r
3916 \r
3917 //backwards compat\r
3918 Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;\r
3919 Ext.ux.GroupTab = Ext.extend(Ext.Container, {\r
3920     mainItem: 0,\r
3921     \r
3922     expanded: true,\r
3923     \r
3924     deferredRender: true,\r
3925     \r
3926     activeTab: null,\r
3927     \r
3928     idDelimiter: '__',\r
3929     \r
3930     headerAsText: false,\r
3931     \r
3932     frame: false,\r
3933     \r
3934     hideBorders: true,\r
3935     \r
3936     initComponent: function(config){\r
3937         Ext.apply(this, config);\r
3938         this.frame = false;\r
3939         \r
3940         Ext.ux.GroupTab.superclass.initComponent.call(this);\r
3941         \r
3942         this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');\r
3943         \r
3944         this.setLayout(new Ext.layout.CardLayout({\r
3945             deferredRender: this.deferredRender\r
3946         }));\r
3947         \r
3948         if (!this.stack) {\r
3949             this.stack = Ext.TabPanel.AccessStack();\r
3950         }\r
3951         \r
3952         this.initItems();\r
3953         \r
3954         this.on('beforerender', function(){\r
3955             this.groupEl = this.ownerCt.getGroupEl(this);\r
3956         }, this);\r
3957         \r
3958         this.on('add', this.onAdd, this, {\r
3959             target: this\r
3960         });\r
3961         this.on('remove', this.onRemove, this, {\r
3962             target: this\r
3963         });\r
3964         \r
3965         if (this.mainItem !== undefined) {\r
3966             var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);\r
3967             delete this.mainItem;\r
3968             this.setMainItem(item);\r
3969         }\r
3970     },\r
3971     \r
3972     /**\r
3973      * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which\r
3974      * can return false to cancel the tab change.\r
3975      * @param {String/Panel} tab The id or tab Panel to activate\r
3976      */\r
3977     setActiveTab : function(item){\r
3978         item = this.getComponent(item);\r
3979         if(!item){\r
3980             return false;\r
3981         }\r
3982         if(!this.rendered){\r
3983             this.activeTab = item;\r
3984             return true;\r
3985         }\r
3986         if(this.activeTab != item && this.fireEvent('beforetabchange', this, item, this.activeTab) !== false){\r
3987             if(this.activeTab && this.activeTab != this.mainItem){\r
3988                 var oldEl = this.getTabEl(this.activeTab);\r
3989                 if(oldEl){\r
3990                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
3991                 }\r
3992             }\r
3993             var el = this.getTabEl(item);\r
3994             Ext.fly(el).addClass('x-grouptabs-strip-active');\r
3995             this.activeTab = item;\r
3996             this.stack.add(item);\r
3997 \r
3998             this.layout.setActiveItem(item);\r
3999             if(this.layoutOnTabChange && item.doLayout){\r
4000                 item.doLayout();\r
4001             }\r
4002             if(this.scrolling){\r
4003                 this.scrollToTab(item, this.animScroll);\r
4004             }\r
4005 \r
4006             this.fireEvent('tabchange', this, item);\r
4007             return true;\r
4008         }\r
4009         return false;\r
4010     },\r
4011     \r
4012     getTabEl: function(item){\r
4013         if (item == this.mainItem) {\r
4014             return this.groupEl;\r
4015         }\r
4016         return Ext.TabPanel.prototype.getTabEl.call(this, item);\r
4017     },\r
4018     \r
4019     onRender: function(ct, position){\r
4020         Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);\r
4021         \r
4022         this.strip = Ext.fly(this.groupEl).createChild({\r
4023             tag: 'ul',\r
4024             cls: 'x-grouptabs-sub'\r
4025         });\r
4026 \r
4027         this.tooltip = new Ext.ToolTip({\r
4028            target: this.groupEl,\r
4029            delegate: 'a.x-grouptabs-text',\r
4030            trackMouse: true,\r
4031            renderTo: document.body,\r
4032            listeners: {\r
4033                beforeshow: function(tip) {\r
4034                    var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)\r
4035                        ? this.mainItem\r
4036                        : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);\r
4037 \r
4038                    if(!item.tabTip) {\r
4039                        return false;\r
4040                    }\r
4041                    tip.body.dom.innerHTML = item.tabTip;\r
4042                },\r
4043                scope: this\r
4044            }\r
4045         });\r
4046                 \r
4047         if (!this.itemTpl) {\r
4048             var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');\r
4049             tt.disableFormats = true;\r
4050             tt.compile();\r
4051             Ext.ux.GroupTab.prototype.itemTpl = tt;\r
4052         }\r
4053         \r
4054         this.items.each(this.initTab, this);\r
4055     },\r
4056     \r
4057     afterRender: function(){\r
4058         Ext.ux.GroupTab.superclass.afterRender.call(this);\r
4059         \r
4060         if (this.activeTab !== undefined) {\r
4061             var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);\r
4062             delete this.activeTab;\r
4063             this.setActiveTab(item);\r
4064         }\r
4065     },\r
4066     \r
4067     // private\r
4068     initTab: function(item, index){\r
4069         var before = this.strip.dom.childNodes[index];\r
4070         var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);\r
4071         \r
4072         if (item === this.mainItem) {\r
4073             item.tabEl = this.groupEl;\r
4074             p.cls += ' x-grouptabs-main-item';\r
4075         }\r
4076         \r
4077         var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
4078         \r
4079         item.tabEl = item.tabEl || el;\r
4080                 \r
4081         item.on('disable', this.onItemDisabled, this);\r
4082         item.on('enable', this.onItemEnabled, this);\r
4083         item.on('titlechange', this.onItemTitleChanged, this);\r
4084         item.on('iconchange', this.onItemIconChanged, this);\r
4085         item.on('beforeshow', this.onBeforeShowItem, this);\r
4086     },\r
4087     \r
4088     setMainItem: function(item){\r
4089         item = this.getComponent(item);\r
4090         if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {\r
4091             return;\r
4092         }\r
4093         \r
4094         this.mainItem = item;\r
4095     },\r
4096     \r
4097     getMainItem: function(){\r
4098         return this.mainItem || null;\r
4099     },\r
4100     \r
4101     // private\r
4102     onBeforeShowItem: function(item){\r
4103         if (item != this.activeTab) {\r
4104             this.setActiveTab(item);\r
4105             return false;\r
4106         }\r
4107     },\r
4108     \r
4109     // private\r
4110     onAdd: function(gt, item, index){\r
4111         if (this.rendered) {\r
4112             this.initTab.call(this, item, index);\r
4113         }\r
4114     },\r
4115     \r
4116     // private\r
4117     onRemove: function(tp, item){\r
4118         Ext.destroy(Ext.get(this.getTabEl(item)));\r
4119         this.stack.remove(item);\r
4120         item.un('disable', this.onItemDisabled, this);\r
4121         item.un('enable', this.onItemEnabled, this);\r
4122         item.un('titlechange', this.onItemTitleChanged, this);\r
4123         item.un('iconchange', this.onItemIconChanged, this);\r
4124         item.un('beforeshow', this.onBeforeShowItem, this);\r
4125         if (item == this.activeTab) {\r
4126             var next = this.stack.next();\r
4127             if (next) {\r
4128                 this.setActiveTab(next);\r
4129             }\r
4130             else if (this.items.getCount() > 0) {\r
4131                 this.setActiveTab(0);\r
4132             }\r
4133             else {\r
4134                 this.activeTab = null;\r
4135             }\r
4136         }\r
4137     },\r
4138     \r
4139     // private\r
4140     onBeforeAdd: function(item){\r
4141         var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);\r
4142         if (existing) {\r
4143             this.setActiveTab(item);\r
4144             return false;\r
4145         }\r
4146         Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);\r
4147         var es = item.elements;\r
4148         item.elements = es ? es.replace(',header', '') : es;\r
4149         item.border = (item.border === true);\r
4150     },\r
4151     \r
4152     // private\r
4153     onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,\r
4154     onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,\r
4155     \r
4156     // private\r
4157     onItemTitleChanged: function(item){\r
4158         var el = this.getTabEl(item);\r
4159         if (el) {\r
4160             Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;\r
4161         }\r
4162     },\r
4163     \r
4164     //private\r
4165     onItemIconChanged: function(item, iconCls, oldCls){\r
4166         var el = this.getTabEl(item);\r
4167         if (el) {\r
4168             Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);\r
4169         }\r
4170     },\r
4171     \r
4172     beforeDestroy: function(){\r
4173         Ext.TabPanel.prototype.beforeDestroy.call(this);\r
4174         this.tooltip.destroy();\r
4175     }\r
4176 });\r
4177 \r
4178 Ext.reg('grouptab', Ext.ux.GroupTab);\r
4179 Ext.ns('Ext.ux');\r
4180 \r
4181 Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {\r
4182     tabPosition: 'left',\r
4183     \r
4184     alternateColor: false,\r
4185     \r
4186     alternateCls: 'x-grouptabs-panel-alt',\r
4187     \r
4188     defaultType: 'grouptab',\r
4189     \r
4190     deferredRender: false,\r
4191     \r
4192     activeGroup : null,\r
4193     \r
4194     initComponent: function(){\r
4195         Ext.ux.GroupTabPanel.superclass.initComponent.call(this);\r
4196         \r
4197         this.addEvents(\r
4198             'beforegroupchange',\r
4199             'groupchange'\r
4200         );\r
4201         this.elements = 'body,header';\r
4202         this.stripTarget = 'header';\r
4203         \r
4204         this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';\r
4205         \r
4206         this.addClass('x-grouptabs-panel');\r
4207         \r
4208         if (this.tabStyle && this.tabStyle != '') {\r
4209             this.addClass('x-grouptabs-panel-' + this.tabStyle);\r
4210         }\r
4211         \r
4212         if (this.alternateColor) {\r
4213             this.addClass(this.alternateCls);\r
4214         }\r
4215         \r
4216         this.on('beforeadd', function(gtp, item, index){\r
4217             this.initGroup(item, index);\r
4218         });                  \r
4219     },\r
4220     \r
4221     initEvents : function() {\r
4222         this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);\r
4223     },\r
4224         \r
4225     onRender: function(ct, position){\r
4226         Ext.TabPanel.superclass.onRender.call(this, ct, position);\r
4227         if(this.plain){\r
4228             var pos = this.tabPosition == 'top' ? 'header' : 'footer';\r
4229             this[pos].addClass('x-tab-panel-'+pos+'-plain');\r
4230         }\r
4231 \r
4232         var st = this[this.stripTarget];\r
4233 \r
4234         this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{\r
4235             tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});\r
4236 \r
4237         var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);\r
4238         this.strip = new Ext.Element(this.stripWrap.dom.firstChild);\r
4239 \r
4240                 this.header.addClass('x-grouptabs-panel-header');\r
4241                 this.bwrap.addClass('x-grouptabs-bwrap');\r
4242         this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');\r
4243 \r
4244         if (!this.groupTpl) {\r
4245             var tt = new Ext.Template(\r
4246                 '<li class="{cls}" id="{id}">', \r
4247                 '<a class="x-grouptabs-expand" onclick="return false;"></a>', \r
4248                 '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',\r
4249                 '<span>{text}</span></a>', \r
4250                 '</li>'\r
4251             );\r
4252             tt.disableFormats = true;\r
4253             tt.compile();\r
4254             Ext.ux.GroupTabPanel.prototype.groupTpl = tt;\r
4255         }\r
4256         this.items.each(this.initGroup, this);\r
4257     },\r
4258     \r
4259     afterRender: function(){\r
4260         Ext.ux.GroupTabPanel.superclass.afterRender.call(this);\r
4261         \r
4262         this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({\r
4263             cls: 'x-tab-joint'\r
4264         });\r
4265         \r
4266         this.addClass('x-tab-panel-' + this.tabPosition);\r
4267         this.header.setWidth(this.tabWidth);\r
4268         \r
4269         if (this.activeGroup !== undefined) {\r
4270             var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);\r
4271             delete this.activeGroup;\r
4272             this.setActiveGroup(group);\r
4273             group.setActiveTab(group.getMainItem());\r
4274         }\r
4275     },\r
4276 \r
4277     getGroupEl : Ext.TabPanel.prototype.getTabEl,\r
4278         \r
4279     // private\r
4280     findTargets: function(e){\r
4281         var item = null,\r
4282             itemEl = e.getTarget('li', this.strip);\r
4283         if (itemEl) {\r
4284             item = this.findById(itemEl.id.split(this.idDelimiter)[1]);\r
4285             if (item.disabled) {\r
4286                 return {\r
4287                     expand: null,\r
4288                     item: null,\r
4289                     el: null\r
4290                 };\r
4291             }\r
4292         }\r
4293         return {\r
4294             expand: e.getTarget('.x-grouptabs-expand', this.strip),\r
4295             isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),\r
4296             item: item,\r
4297             el: itemEl\r
4298         };\r
4299     },\r
4300     \r
4301     // private\r
4302     onStripMouseDown: function(e){\r
4303         if (e.button != 0) {\r
4304             return;\r
4305         }\r
4306         e.preventDefault();\r
4307         var t = this.findTargets(e);\r
4308         if (t.expand) {\r
4309             this.toggleGroup(t.el);\r
4310         }\r
4311         else if (t.item) {\r
4312             if(t.isGroup) {\r
4313                 t.item.setActiveTab(t.item.getMainItem());\r
4314             }\r
4315             else {\r
4316                 t.item.ownerCt.setActiveTab(t.item);\r
4317             }\r
4318         }\r
4319     },\r
4320     \r
4321     expandGroup: function(groupEl){\r
4322         if(groupEl.isXType) {\r
4323             groupEl = this.getGroupEl(groupEl);\r
4324         }\r
4325         Ext.fly(groupEl).addClass('x-grouptabs-expanded');\r
4326                 this.syncTabJoint();\r
4327     },\r
4328     \r
4329     toggleGroup: function(groupEl){\r
4330         if(groupEl.isXType) {\r
4331             groupEl = this.getGroupEl(groupEl);\r
4332         }        \r
4333         Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');\r
4334                 this.syncTabJoint();\r
4335     },    \r
4336 \r
4337     collapseGroup: function(groupEl){\r
4338         if(groupEl.isXType) {\r
4339             groupEl = this.getGroupEl(groupEl);\r
4340         }\r
4341         Ext.fly(groupEl).removeClass('x-grouptabs-expanded');\r
4342                 this.syncTabJoint();\r
4343     },\r
4344         \r
4345     syncTabJoint: function(groupEl){\r
4346         if (!this.tabJoint) {\r
4347             return;\r
4348         }\r
4349         \r
4350         groupEl = groupEl || this.getGroupEl(this.activeGroup);\r
4351         if(groupEl) {\r
4352             this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); \r
4353                         \r
4354             var y = Ext.isGecko2 ? 0 : 1;\r
4355             if (this.tabPosition == 'left'){\r
4356                 this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);\r
4357             }\r
4358             else {\r
4359                 this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);\r
4360             }           \r
4361         }\r
4362         else {\r
4363             this.tabJoint.hide();\r
4364         }\r
4365     },\r
4366     \r
4367     getActiveTab : function() {\r
4368         if(!this.activeGroup) return null;\r
4369         return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null;  \r
4370     },\r
4371     \r
4372     onResize: function(){\r
4373         Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);\r
4374         this.syncTabJoint();\r
4375     },\r
4376     \r
4377     createCorner: function(el, pos){\r
4378         return Ext.fly(el).createChild({\r
4379             cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos\r
4380         });\r
4381     },\r
4382     \r
4383     initGroup: function(group, index){\r
4384         var before = this.strip.dom.childNodes[index],   \r
4385             p = this.getTemplateArgs(group);\r
4386         if (index === 0) {\r
4387             p.cls += ' x-tab-first';\r
4388         }\r
4389         p.cls += ' x-grouptabs-main';\r
4390         p.text = group.getMainItem().title;\r
4391         \r
4392         var el = before ? this.groupTpl.insertBefore(before, p) : this.groupTpl.append(this.strip, p),\r
4393             tl = this.createCorner(el, 'top-' + this.tabPosition),\r
4394             bl = this.createCorner(el, 'bottom-' + this.tabPosition);\r
4395 \r
4396         group.tabEl = el;\r
4397         if (group.expanded) {\r
4398             this.expandGroup(el);\r
4399         }\r
4400 \r
4401         if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){\r
4402             bl.setLeft('-10px');\r
4403             bl.setBottom('-5px');\r
4404             tl.setLeft('-10px');\r
4405             tl.setTop('-5px');\r
4406         }\r
4407 \r
4408         this.mon(group, {\r
4409             scope: this,\r
4410             changemainitem: this.onGroupChangeMainItem,\r
4411             beforetabchange: this.onGroupBeforeTabChange\r
4412         });\r
4413     },\r
4414     \r
4415     setActiveGroup : function(group) {\r
4416         group = this.getComponent(group);\r
4417         if(!group){\r
4418             return false;\r
4419         }\r
4420         if(!this.rendered){\r
4421             this.activeGroup = group;\r
4422             return true;\r
4423         }\r
4424         if(this.activeGroup != group && this.fireEvent('beforegroupchange', this, group, this.activeGroup) !== false){\r
4425             if(this.activeGroup){\r
4426                 var oldEl = this.getGroupEl(this.activeGroup);\r
4427                 if(oldEl){\r
4428                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
4429                 }\r
4430             }\r
4431 \r
4432             var groupEl = this.getGroupEl(group);\r
4433             Ext.fly(groupEl).addClass('x-grouptabs-strip-active');\r
4434                         \r
4435             this.activeGroup = group;\r
4436             this.stack.add(group);\r
4437 \r
4438             this.layout.setActiveItem(group);\r
4439             this.syncTabJoint(groupEl);\r
4440 \r
4441             this.fireEvent('groupchange', this, group);\r
4442             return true;\r
4443         }\r
4444         return false; \r
4445     },\r
4446     \r
4447     onGroupBeforeTabChange: function(group, newTab, oldTab){\r
4448         if(group !== this.activeGroup || newTab !== oldTab) {\r
4449             this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');\r
4450         } \r
4451         this.expandGroup(this.getGroupEl(group));\r
4452         if(group !== this.activeGroup) {\r
4453             return this.setActiveGroup(group);\r
4454         }        \r
4455     },\r
4456     \r
4457     getFrameHeight: function(){\r
4458         var h = this.el.getFrameWidth('tb');\r
4459         h += (this.tbar ? this.tbar.getHeight() : 0) +\r
4460         (this.bbar ? this.bbar.getHeight() : 0);\r
4461         \r
4462         return h;\r
4463     },\r
4464     \r
4465     adjustBodyWidth: function(w){\r
4466         return w - this.tabWidth;\r
4467     }\r
4468 });\r
4469 \r
4470 Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/*\r
4471  * Note that this control will most likely remain as an example, and not as a core Ext form\r
4472  * control.  However, the API will be changing in a future release and so should not yet be\r
4473  * treated as a final, stable API at this time.\r
4474  */\r
4475 \r
4476 /**\r
4477  * @class Ext.ux.form.ItemSelector\r
4478  * @extends Ext.form.Field\r
4479  * A control that allows selection of between two Ext.ux.form.MultiSelect controls.\r
4480  *\r
4481  *  @history\r
4482  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
4483  *\r
4484  * @constructor\r
4485  * Create a new ItemSelector\r
4486  * @param {Object} config Configuration options\r
4487  * @xtype itemselector \r
4488  */\r
4489 Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {\r
4490     hideNavIcons:false,\r
4491     imagePath:"",\r
4492     iconUp:"up2.gif",\r
4493     iconDown:"down2.gif",\r
4494     iconLeft:"left2.gif",\r
4495     iconRight:"right2.gif",\r
4496     iconTop:"top2.gif",\r
4497     iconBottom:"bottom2.gif",\r
4498     drawUpIcon:true,\r
4499     drawDownIcon:true,\r
4500     drawLeftIcon:true,\r
4501     drawRightIcon:true,\r
4502     drawTopIcon:true,\r
4503     drawBotIcon:true,\r
4504     delimiter:',',\r
4505     bodyStyle:null,\r
4506     border:false,\r
4507     defaultAutoCreate:{tag: "div"},\r
4508     /**\r
4509      * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)\r
4510      */\r
4511     multiselects:null,\r
4512 \r
4513     initComponent: function(){\r
4514         Ext.ux.form.ItemSelector.superclass.initComponent.call(this);\r
4515         this.addEvents({\r
4516             'rowdblclick' : true,\r
4517             'change' : true\r
4518         });\r
4519     },\r
4520 \r
4521     onRender: function(ct, position){\r
4522         Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);\r
4523 \r
4524         // Internal default configuration for both multiselects\r
4525         var msConfig = [{\r
4526             legend: 'Available',\r
4527             draggable: true,\r
4528             droppable: true,\r
4529             width: 100,\r
4530             height: 100\r
4531         },{\r
4532             legend: 'Selected',\r
4533             droppable: true,\r
4534             draggable: true,\r
4535             width: 100,\r
4536             height: 100\r
4537         }];\r
4538 \r
4539         this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));\r
4540         this.fromMultiselect.on('dblclick', this.onRowDblClick, this);\r
4541 \r
4542         this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));\r
4543         this.toMultiselect.on('dblclick', this.onRowDblClick, this);\r
4544 \r
4545         var p = new Ext.Panel({\r
4546             bodyStyle:this.bodyStyle,\r
4547             border:this.border,\r
4548             layout:"table",\r
4549             layoutConfig:{columns:3}\r
4550         });\r
4551 \r
4552         p.add(this.fromMultiselect);\r
4553         var icons = new Ext.Panel({header:false});\r
4554         p.add(icons);\r
4555         p.add(this.toMultiselect);\r
4556         p.render(this.el);\r
4557         icons.el.down('.'+icons.bwrapCls).remove();\r
4558 \r
4559         // ICON HELL!!!\r
4560         if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")\r
4561             this.imagePath+="/";\r
4562         this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');\r
4563         this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');\r
4564         this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');\r
4565         this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');\r
4566         this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');\r
4567         this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');\r
4568         var el=icons.getEl();\r
4569         this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});\r
4570         el.createChild({tag: 'br'});\r
4571         this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});\r
4572         el.createChild({tag: 'br'});\r
4573         this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});\r
4574         el.createChild({tag: 'br'});\r
4575         this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});\r
4576         el.createChild({tag: 'br'});\r
4577         this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});\r
4578         el.createChild({tag: 'br'});\r
4579         this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});\r
4580         this.toTopIcon.on('click', this.toTop, this);\r
4581         this.upIcon.on('click', this.up, this);\r
4582         this.downIcon.on('click', this.down, this);\r
4583         this.toBottomIcon.on('click', this.toBottom, this);\r
4584         this.addIcon.on('click', this.fromTo, this);\r
4585         this.removeIcon.on('click', this.toFrom, this);\r
4586         if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }\r
4587         if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }\r
4588         if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }\r
4589         if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }\r
4590         if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }\r
4591         if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }\r
4592 \r
4593         var tb = p.body.first();\r
4594         this.el.setWidth(p.body.first().getWidth());\r
4595         p.body.removeClass();\r
4596 \r
4597         this.hiddenName = this.name;\r
4598         var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};\r
4599         this.hiddenField = this.el.createChild(hiddenTag);\r
4600     },\r
4601     \r
4602     doLayout: function(){\r
4603         if(this.rendered){\r
4604             this.fromMultiselect.fs.doLayout();\r
4605             this.toMultiselect.fs.doLayout();\r
4606         }\r
4607     },\r
4608 \r
4609     afterRender: function(){\r
4610         Ext.ux.form.ItemSelector.superclass.afterRender.call(this);\r
4611 \r
4612         this.toStore = this.toMultiselect.store;\r
4613         this.toStore.on('add', this.valueChanged, this);\r
4614         this.toStore.on('remove', this.valueChanged, this);\r
4615         this.toStore.on('load', this.valueChanged, this);\r
4616         this.valueChanged(this.toStore);\r
4617     },\r
4618 \r
4619     toTop : function() {\r
4620         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4621         var records = [];\r
4622         if (selectionsArray.length > 0) {\r
4623             selectionsArray.sort();\r
4624             for (var i=0; i<selectionsArray.length; i++) {\r
4625                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4626                 records.push(record);\r
4627             }\r
4628             selectionsArray = [];\r
4629             for (var i=records.length-1; i>-1; i--) {\r
4630                 record = records[i];\r
4631                 this.toMultiselect.view.store.remove(record);\r
4632                 this.toMultiselect.view.store.insert(0, record);\r
4633                 selectionsArray.push(((records.length - 1) - i));\r
4634             }\r
4635         }\r
4636         this.toMultiselect.view.refresh();\r
4637         this.toMultiselect.view.select(selectionsArray);\r
4638     },\r
4639 \r
4640     toBottom : function() {\r
4641         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4642         var records = [];\r
4643         if (selectionsArray.length > 0) {\r
4644             selectionsArray.sort();\r
4645             for (var i=0; i<selectionsArray.length; i++) {\r
4646                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4647                 records.push(record);\r
4648             }\r
4649             selectionsArray = [];\r
4650             for (var i=0; i<records.length; i++) {\r
4651                 record = records[i];\r
4652                 this.toMultiselect.view.store.remove(record);\r
4653                 this.toMultiselect.view.store.add(record);\r
4654                 selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));\r
4655             }\r
4656         }\r
4657         this.toMultiselect.view.refresh();\r
4658         this.toMultiselect.view.select(selectionsArray);\r
4659     },\r
4660 \r
4661     up : function() {\r
4662         var record = null;\r
4663         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4664         selectionsArray.sort();\r
4665         var newSelectionsArray = [];\r
4666         if (selectionsArray.length > 0) {\r
4667             for (var i=0; i<selectionsArray.length; i++) {\r
4668                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4669                 if ((selectionsArray[i] - 1) >= 0) {\r
4670                     this.toMultiselect.view.store.remove(record);\r
4671                     this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);\r
4672                     newSelectionsArray.push(selectionsArray[i] - 1);\r
4673                 }\r
4674             }\r
4675             this.toMultiselect.view.refresh();\r
4676             this.toMultiselect.view.select(newSelectionsArray);\r
4677         }\r
4678     },\r
4679 \r
4680     down : function() {\r
4681         var record = null;\r
4682         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4683         selectionsArray.sort();\r
4684         selectionsArray.reverse();\r
4685         var newSelectionsArray = [];\r
4686         if (selectionsArray.length > 0) {\r
4687             for (var i=0; i<selectionsArray.length; i++) {\r
4688                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4689                 if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {\r
4690                     this.toMultiselect.view.store.remove(record);\r
4691                     this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);\r
4692                     newSelectionsArray.push(selectionsArray[i] + 1);\r
4693                 }\r
4694             }\r
4695             this.toMultiselect.view.refresh();\r
4696             this.toMultiselect.view.select(newSelectionsArray);\r
4697         }\r
4698     },\r
4699 \r
4700     fromTo : function() {\r
4701         var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();\r
4702         var records = [];\r
4703         if (selectionsArray.length > 0) {\r
4704             for (var i=0; i<selectionsArray.length; i++) {\r
4705                 record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);\r
4706                 records.push(record);\r
4707             }\r
4708             if(!this.allowDup)selectionsArray = [];\r
4709             for (var i=0; i<records.length; i++) {\r
4710                 record = records[i];\r
4711                 if(this.allowDup){\r
4712                     var x=new Ext.data.Record();\r
4713                     record.id=x.id;\r
4714                     delete x;\r
4715                     this.toMultiselect.view.store.add(record);\r
4716                 }else{\r
4717                     this.fromMultiselect.view.store.remove(record);\r
4718                     this.toMultiselect.view.store.add(record);\r
4719                     selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));\r
4720                 }\r
4721             }\r
4722         }\r
4723         this.toMultiselect.view.refresh();\r
4724         this.fromMultiselect.view.refresh();\r
4725         var si = this.toMultiselect.store.sortInfo;\r
4726         if(si){\r
4727             this.toMultiselect.store.sort(si.field, si.direction);\r
4728         }\r
4729         this.toMultiselect.view.select(selectionsArray);\r
4730     },\r
4731 \r
4732     toFrom : function() {\r
4733         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4734         var records = [];\r
4735         if (selectionsArray.length > 0) {\r
4736             for (var i=0; i<selectionsArray.length; i++) {\r
4737                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4738                 records.push(record);\r
4739             }\r
4740             selectionsArray = [];\r
4741             for (var i=0; i<records.length; i++) {\r
4742                 record = records[i];\r
4743                 this.toMultiselect.view.store.remove(record);\r
4744                 if(!this.allowDup){\r
4745                     this.fromMultiselect.view.store.add(record);\r
4746                     selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));\r
4747                 }\r
4748             }\r
4749         }\r
4750         this.fromMultiselect.view.refresh();\r
4751         this.toMultiselect.view.refresh();\r
4752         var si = this.fromMultiselect.store.sortInfo;\r
4753         if (si){\r
4754             this.fromMultiselect.store.sort(si.field, si.direction);\r
4755         }\r
4756         this.fromMultiselect.view.select(selectionsArray);\r
4757     },\r
4758 \r
4759     valueChanged: function(store) {\r
4760         var record = null;\r
4761         var values = [];\r
4762         for (var i=0; i<store.getCount(); i++) {\r
4763             record = store.getAt(i);\r
4764             values.push(record.get(this.toMultiselect.valueField));\r
4765         }\r
4766         this.hiddenField.dom.value = values.join(this.delimiter);\r
4767         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
4768     },\r
4769 \r
4770     getValue : function() {\r
4771         return this.hiddenField.dom.value;\r
4772     },\r
4773 \r
4774     onRowDblClick : function(vw, index, node, e) {\r
4775         if (vw == this.toMultiselect.view){\r
4776             this.toFrom();\r
4777         } else if (vw == this.fromMultiselect.view) {\r
4778             this.fromTo();\r
4779         }\r
4780         return this.fireEvent('rowdblclick', vw, index, node, e);\r
4781     },\r
4782 \r
4783     reset: function(){\r
4784         range = this.toMultiselect.store.getRange();\r
4785         this.toMultiselect.store.removeAll();\r
4786         this.fromMultiselect.store.add(range);\r
4787         var si = this.fromMultiselect.store.sortInfo;\r
4788         if (si){\r
4789             this.fromMultiselect.store.sort(si.field, si.direction);\r
4790         }\r
4791         this.valueChanged(this.toMultiselect.store);\r
4792     }\r
4793 });\r
4794 \r
4795 Ext.reg('itemselector', Ext.ux.form.ItemSelector);\r
4796 \r
4797 //backwards compat\r
4798 Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;\r
4799 Ext.ns('Ext.ux.grid');\r
4800 \r
4801 Ext.ux.grid.LockingGridView = Ext.extend(Ext.grid.GridView, {\r
4802     lockText : 'Lock',\r
4803     unlockText : 'Unlock',\r
4804     rowBorderWidth : 1,\r
4805     lockedBorderWidth : 1,\r
4806     /*\r
4807      * This option ensures that height between the rows is synchronized\r
4808      * between the locked and unlocked sides. This option only needs to be used\r
4809      * when the row heights isn't predictable.\r
4810      */\r
4811     syncHeights: false,\r
4812     initTemplates : function(){\r
4813         var ts = this.templates || {};\r
4814         if(!ts.master){\r
4815             ts.master = new Ext.Template(\r
4816                 '<div class="x-grid3" hidefocus="true">',\r
4817                     '<div class="x-grid3-locked">',\r
4818                         '<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
4819                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{lstyle}">{lockedBody}</div><div class="x-grid3-scroll-spacer"></div></div>',\r
4820                     '</div>',\r
4821                     '<div class="x-grid3-viewport x-grid3-unlocked">',\r
4822                         '<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
4823                         '<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
4824                     '</div>',\r
4825                     '<div class="x-grid3-resize-marker">&#160;</div>',\r
4826                     '<div class="x-grid3-resize-proxy">&#160;</div>',\r
4827                 '</div>'\r
4828             );\r
4829         }\r
4830         this.templates = ts;\r
4831         Ext.ux.grid.LockingGridView.superclass.initTemplates.call(this);\r
4832     },\r
4833     getEditorParent : function(ed){\r
4834         return this.el.dom;\r
4835     },\r
4836     initElements : function(){\r
4837         var E = Ext.Element;\r
4838         var el = this.grid.getGridEl().dom.firstChild;\r
4839         var cs = el.childNodes;\r
4840         this.el = new E(el);\r
4841         this.lockedWrap = new E(cs[0]);\r
4842         this.lockedHd = new E(this.lockedWrap.dom.firstChild);\r
4843         this.lockedInnerHd = this.lockedHd.dom.firstChild;\r
4844         this.lockedScroller = new E(this.lockedWrap.dom.childNodes[1]);\r
4845         this.lockedBody = new E(this.lockedScroller.dom.firstChild);\r
4846         this.mainWrap = new E(cs[1]);\r
4847         this.mainHd = new E(this.mainWrap.dom.firstChild);\r
4848         if(this.grid.hideHeaders){\r
4849             this.lockedHd.setDisplayed(false);\r
4850             this.mainHd.setDisplayed(false);\r
4851         }\r
4852         this.innerHd = this.mainHd.dom.firstChild;\r
4853         this.scroller = new E(this.mainWrap.dom.childNodes[1]);\r
4854         if(this.forceFit){\r
4855             this.scroller.setStyle('overflow-x', 'hidden');\r
4856         }\r
4857         this.mainBody = new E(this.scroller.dom.firstChild);\r
4858         this.focusEl = new E(this.scroller.dom.childNodes[1]);\r
4859         this.focusEl.swallowEvent('click', true);\r
4860         this.resizeMarker = new E(cs[2]);\r
4861         this.resizeProxy = new E(cs[3]);\r
4862     },\r
4863     \r
4864     getLockedRows : function(){\r
4865         return this.hasRows() ? this.lockedBody.dom.childNodes : [];\r
4866     },\r
4867     \r
4868     getLockedRow : function(row){\r
4869         return this.getLockedRows()[row];\r
4870     },\r
4871     \r
4872     getCell : function(row, col){\r
4873         var llen = this.cm.getLockedCount();\r
4874         if(col < llen){\r
4875             return this.getLockedRow(row).getElementsByTagName('td')[col];\r
4876         }\r
4877         return Ext.ux.grid.LockingGridView.superclass.getCell.call(this, row, col - llen);\r
4878     },\r
4879     \r
4880     getHeaderCell : function(index){\r
4881         var llen = this.cm.getLockedCount();\r
4882         if(index < llen){\r
4883             return this.lockedHd.dom.getElementsByTagName('td')[index];\r
4884         }\r
4885         return Ext.ux.grid.LockingGridView.superclass.getHeaderCell.call(this, index - llen);\r
4886     },\r
4887     \r
4888     addRowClass : function(row, cls){\r
4889         var r = this.getLockedRow(row);\r
4890         if(r){\r
4891             this.fly(r).addClass(cls);\r
4892         }\r
4893         Ext.ux.grid.LockingGridView.superclass.addRowClass.call(this, row, cls);\r
4894     },\r
4895     \r
4896     removeRowClass : function(row, cls){\r
4897         var r = this.getLockedRow(row);\r
4898         if(r){\r
4899             this.fly(r).removeClass(cls);\r
4900         }\r
4901         Ext.ux.grid.LockingGridView.superclass.removeRowClass.call(this, row, cls);\r
4902     },\r
4903     \r
4904     removeRow : function(row) {\r
4905         Ext.removeNode(this.getLockedRow(row));\r
4906         Ext.ux.grid.LockingGridView.superclass.removeRow.call(this, row);\r
4907     },\r
4908     \r
4909     removeRows : function(firstRow, lastRow){\r
4910         var bd = this.lockedBody.dom;\r
4911         for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){\r
4912             Ext.removeNode(bd.childNodes[firstRow]);\r
4913         }\r
4914         Ext.ux.grid.LockingGridView.superclass.removeRows.call(this, firstRow, lastRow);\r
4915     },\r
4916     \r
4917     syncScroll : function(e){\r
4918         var mb = this.scroller.dom;\r
4919         this.lockedScroller.dom.scrollTop = mb.scrollTop;\r
4920         Ext.ux.grid.LockingGridView.superclass.syncScroll.call(this, e);\r
4921     },\r
4922     \r
4923     updateSortIcon : function(col, dir){\r
4924         var sc = this.sortClasses,\r
4925             lhds = this.lockedHd.select('td').removeClass(sc),\r
4926             hds = this.mainHd.select('td').removeClass(sc),\r
4927             llen = this.cm.getLockedCount(),\r
4928             cls = sc[dir == 'DESC' ? 1 : 0];\r
4929         if(col < llen){\r
4930             lhds.item(col).addClass(cls);\r
4931         }else{\r
4932             hds.item(col - llen).addClass(cls);\r
4933         }\r
4934     },\r
4935     \r
4936     updateAllColumnWidths : function(){\r
4937         var tw = this.getTotalWidth(),\r
4938             clen = this.cm.getColumnCount(),\r
4939             lw = this.getLockedWidth(),\r
4940             llen = this.cm.getLockedCount(),\r
4941             ws = [], len, i;\r
4942         this.updateLockedWidth();\r
4943         for(i = 0; i < clen; i++){\r
4944             ws[i] = this.getColumnWidth(i);\r
4945             var hd = this.getHeaderCell(i);\r
4946             hd.style.width = ws[i];\r
4947         }\r
4948         var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j;\r
4949         for(i = 0, len = ns.length; i < len; i++){\r
4950             row = lns[i];\r
4951             row.style.width = lw;\r
4952             if(row.firstChild){\r
4953                 row.firstChild.style.width = lw;\r
4954                 trow = row.firstChild.rows[0];\r
4955                 for (j = 0; j < llen; j++) {\r
4956                    trow.childNodes[j].style.width = ws[j];\r
4957                 }\r
4958             }\r
4959             row = ns[i];\r
4960             row.style.width = tw;\r
4961             if(row.firstChild){\r
4962                 row.firstChild.style.width = tw;\r
4963                 trow = row.firstChild.rows[0];\r
4964                 for (j = llen; j < clen; j++) {\r
4965                    trow.childNodes[j - llen].style.width = ws[j];\r
4966                 }\r
4967             }\r
4968         }\r
4969         this.onAllColumnWidthsUpdated(ws, tw);\r
4970         this.syncHeaderHeight();\r
4971     },\r
4972     \r
4973     updateColumnWidth : function(col, width){\r
4974         var w = this.getColumnWidth(col),\r
4975             llen = this.cm.getLockedCount(),\r
4976             ns, rw, c, row;\r
4977         this.updateLockedWidth();\r
4978         if(col < llen){\r
4979             ns = this.getLockedRows();\r
4980             rw = this.getLockedWidth();\r
4981             c = col;\r
4982         }else{\r
4983             ns = this.getRows();\r
4984             rw = this.getTotalWidth();\r
4985             c = col - llen;\r
4986         }\r
4987         var hd = this.getHeaderCell(col);\r
4988         hd.style.width = w;\r
4989         for(var i = 0, len = ns.length; i < len; i++){\r
4990             row = ns[i];\r
4991             row.style.width = rw;\r
4992             if(row.firstChild){\r
4993                 row.firstChild.style.width = rw;\r
4994                 row.firstChild.rows[0].childNodes[c].style.width = w;\r
4995             }\r
4996         }\r
4997         this.onColumnWidthUpdated(col, w, this.getTotalWidth());\r
4998         this.syncHeaderHeight();\r
4999     },\r
5000     \r
5001     updateColumnHidden : function(col, hidden){\r
5002         var llen = this.cm.getLockedCount(),\r
5003             ns, rw, c, row,\r
5004             display = hidden ? 'none' : '';\r
5005         this.updateLockedWidth();\r
5006         if(col < llen){\r
5007             ns = this.getLockedRows();\r
5008             rw = this.getLockedWidth();\r
5009             c = col;\r
5010         }else{\r
5011             ns = this.getRows();\r
5012             rw = this.getTotalWidth();\r
5013             c = col - llen;\r
5014         }\r
5015         var hd = this.getHeaderCell(col);\r
5016         hd.style.display = display;\r
5017         for(var i = 0, len = ns.length; i < len; i++){\r
5018             row = ns[i];\r
5019             row.style.width = rw;\r
5020             if(row.firstChild){\r
5021                 row.firstChild.style.width = rw;\r
5022                 row.firstChild.rows[0].childNodes[c].style.display = display;\r
5023             }\r
5024         }\r
5025         this.onColumnHiddenUpdated(col, hidden, this.getTotalWidth());\r
5026         delete this.lastViewWidth;\r
5027         this.layout();\r
5028     },\r
5029     \r
5030     doRender : function(cs, rs, ds, startRow, colCount, stripe){\r
5031         var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1,\r
5032             tstyle = 'width:'+this.getTotalWidth()+';',\r
5033             lstyle = 'width:'+this.getLockedWidth()+';',\r
5034             buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r;\r
5035         for(var j = 0, len = rs.length; j < len; j++){\r
5036             r = rs[j]; cb = []; lcb = [];\r
5037             var rowIndex = (j+startRow);\r
5038             for(var i = 0; i < colCount; i++){\r
5039                 c = cs[i];\r
5040                 p.id = c.id;\r
5041                 p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +\r
5042                     (this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : '');\r
5043                 p.attr = p.cellAttr = '';\r
5044                 p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);\r
5045                 p.style = c.style;\r
5046                 if(Ext.isEmpty(p.value)){\r
5047                     p.value = '&#160;';\r
5048                 }\r
5049                 if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){\r
5050                     p.css += ' x-grid3-dirty-cell';\r
5051                 }\r
5052                 if(c.locked){\r
5053                     lcb[lcb.length] = ct.apply(p);\r
5054                 }else{\r
5055                     cb[cb.length] = ct.apply(p);\r
5056                 }\r
5057             }\r
5058             var alt = [];\r
5059             if(stripe && ((rowIndex+1) % 2 === 0)){\r
5060                 alt[0] = 'x-grid3-row-alt';\r
5061             }\r
5062             if(r.dirty){\r
5063                 alt[1] = ' x-grid3-dirty-row';\r
5064             }\r
5065             rp.cols = colCount;\r
5066             if(this.getRowClass){\r
5067                 alt[2] = this.getRowClass(r, rowIndex, rp, ds);\r
5068             }\r
5069             rp.alt = alt.join(' ');\r
5070             rp.cells = cb.join('');\r
5071             rp.tstyle = tstyle;\r
5072             buf[buf.length] = rt.apply(rp);\r
5073             rp.cells = lcb.join('');\r
5074             rp.tstyle = lstyle;\r
5075             lbuf[lbuf.length] = rt.apply(rp);\r
5076         }\r
5077         return [buf.join(''), lbuf.join('')];\r
5078     },\r
5079     processRows : function(startRow, skipStripe){\r
5080         if(!this.ds || this.ds.getCount() < 1){\r
5081             return;\r
5082         }\r
5083         var rows = this.getRows(),\r
5084             lrows = this.getLockedRows(),\r
5085             row, lrow;\r
5086         skipStripe = skipStripe || !this.grid.stripeRows;\r
5087         startRow = startRow || 0;\r
5088         for(var i = 0, len = rows.length; i < len; ++i){\r
5089             row = rows[i];\r
5090             lrow = lrows[i];\r
5091             row.rowIndex = i;\r
5092             lrow.rowIndex = i;\r
5093             if(!skipStripe){\r
5094                 row.className = row.className.replace(this.rowClsRe, ' ');\r
5095                 lrow.className = lrow.className.replace(this.rowClsRe, ' ');\r
5096                 if ((idx + 1) % 2 === 0){\r
5097                     row.className += ' x-grid3-row-alt';\r
5098                     lrow.className += ' x-grid3-row-alt';\r
5099                 }\r
5100             }\r
5101             if(this.syncHeights){\r
5102                 var el1 = Ext.get(row),\r
5103                     el2 = Ext.get(lrow),\r
5104                     h1 = el1.getHeight(),\r
5105                     h2 = el2.getHeight();\r
5106                 \r
5107                 if(h1 > h2){\r
5108                     el2.setHeight(h1);    \r
5109                 }else if(h2 > h1){\r
5110                     el1.setHeight(h2);\r
5111                 }\r
5112             }\r
5113         }\r
5114         if(startRow === 0){\r
5115             Ext.fly(rows[0]).addClass(this.firstRowCls);\r
5116             Ext.fly(lrows[0]).addClass(this.firstRowCls);\r
5117         }\r
5118         Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);\r
5119         Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls);\r
5120     },\r
5121     \r
5122     afterRender : function(){\r
5123         if(!this.ds || !this.cm){\r
5124             return;\r
5125         }\r
5126         var bd = this.renderRows() || ['&#160;', '&#160;'];\r
5127         this.mainBody.dom.innerHTML = bd[0];\r
5128         this.lockedBody.dom.innerHTML = bd[1];\r
5129         this.processRows(0, true);\r
5130         if(this.deferEmptyText !== true){\r
5131             this.applyEmptyText();\r
5132         }\r
5133     },\r
5134     \r
5135     renderUI : function(){\r
5136         var header = this.renderHeaders();\r
5137         var body = this.templates.body.apply({rows:'&#160;'});\r
5138         var html = this.templates.master.apply({\r
5139             body: body,\r
5140             header: header[0],\r
5141             ostyle: 'width:'+this.getOffsetWidth()+';',\r
5142             bstyle: 'width:'+this.getTotalWidth()+';',\r
5143             lockedBody: body,\r
5144             lockedHeader: header[1],\r
5145             lstyle: 'width:'+this.getLockedWidth()+';'\r
5146         });\r
5147         var g = this.grid;\r
5148         g.getGridEl().dom.innerHTML = html;\r
5149         this.initElements();\r
5150         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);\r
5151         Ext.fly(this.lockedInnerHd).on('click', this.handleHdDown, this);\r
5152         this.mainHd.on({\r
5153             scope: this,\r
5154             mouseover: this.handleHdOver,\r
5155             mouseout: this.handleHdOut,\r
5156             mousemove: this.handleHdMove\r
5157         });\r
5158         this.lockedHd.on({\r
5159             scope: this,\r
5160             mouseover: this.handleHdOver,\r
5161             mouseout: this.handleHdOut,\r
5162             mousemove: this.handleHdMove\r
5163         });\r
5164         this.scroller.on('scroll', this.syncScroll,  this);\r
5165         if(g.enableColumnResize !== false){\r
5166             this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);\r
5167             this.splitZone.setOuterHandleElId(Ext.id(this.lockedHd.dom));\r
5168             this.splitZone.setOuterHandleElId(Ext.id(this.mainHd.dom));\r
5169         }\r
5170         if(g.enableColumnMove){\r
5171             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);\r
5172             this.columnDrag.setOuterHandleElId(Ext.id(this.lockedInnerHd));\r
5173             this.columnDrag.setOuterHandleElId(Ext.id(this.innerHd));\r
5174             this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);\r
5175         }\r
5176         if(g.enableHdMenu !== false){\r
5177             this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});\r
5178             this.hmenu.add(\r
5179                 {itemId: 'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},\r
5180                 {itemId: 'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}\r
5181             );\r
5182             if(this.grid.enableColLock !== false){\r
5183                 this.hmenu.add('-',\r
5184                     {itemId: 'lock', text: this.lockText, cls: 'xg-hmenu-lock'},\r
5185                     {itemId: 'unlock', text: this.unlockText, cls: 'xg-hmenu-unlock'}\r
5186                 );\r
5187             }\r
5188             if(g.enableColumnHide !== false){\r
5189                 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});\r
5190                 this.colMenu.on({\r
5191                     scope: this,\r
5192                     beforeshow: this.beforeColMenuShow,\r
5193                     itemclick: this.handleHdMenuClick\r
5194                 });\r
5195                 this.hmenu.add('-', {\r
5196                     itemId:'columns',\r
5197                     hideOnClick: false,\r
5198                     text: this.columnsText,\r
5199                     menu: this.colMenu,\r
5200                     iconCls: 'x-cols-icon'\r
5201                 });\r
5202             }\r
5203             this.hmenu.on('itemclick', this.handleHdMenuClick, this);\r
5204         }\r
5205         if(g.trackMouseOver){\r
5206             this.mainBody.on({\r
5207                 scope: this,\r
5208                 mouseover: this.onRowOver,\r
5209                 mouseout: this.onRowOut\r
5210             });\r
5211             this.lockedBody.on({\r
5212                 scope: this,\r
5213                 mouseover: this.onRowOver,\r
5214                 mouseout: this.onRowOut\r
5215             });\r
5216         }\r
5217         \r
5218         if(g.enableDragDrop || g.enableDrag){\r
5219             this.dragZone = new Ext.grid.GridDragZone(g, {\r
5220                 ddGroup : g.ddGroup || 'GridDD'\r
5221             });\r
5222         }\r
5223         this.updateHeaderSortState();\r
5224     },\r
5225     \r
5226     layout : function(){\r
5227         if(!this.mainBody){\r
5228             return;\r
5229         }\r
5230         var g = this.grid;\r
5231         var c = g.getGridEl();\r
5232         var csize = c.getSize(true);\r
5233         var vw = csize.width;\r
5234         if(!g.hideHeaders && (vw < 20 || csize.height < 20)){\r
5235             return;\r
5236         }\r
5237         this.syncHeaderHeight();\r
5238         if(g.autoHeight){\r
5239             this.scroller.dom.style.overflow = 'visible';\r
5240             this.lockedScroller.dom.style.overflow = 'visible';\r
5241             if(Ext.isWebKit){\r
5242                 this.scroller.dom.style.position = 'static';\r
5243                 this.lockedScroller.dom.style.position = 'static';\r
5244             }\r
5245         }else{\r
5246             this.el.setSize(csize.width, csize.height);\r
5247             var hdHeight = this.mainHd.getHeight();\r
5248             var vh = csize.height - (hdHeight);\r
5249         }\r
5250         this.updateLockedWidth();\r
5251         if(this.forceFit){\r
5252             if(this.lastViewWidth != vw){\r
5253                 this.fitColumns(false, false);\r
5254                 this.lastViewWidth = vw;\r
5255             }\r
5256         }else {\r
5257             this.autoExpand();\r
5258             this.syncHeaderScroll();\r
5259         }\r
5260         this.onLayout(vw, vh);\r
5261     },\r
5262     \r
5263     getOffsetWidth : function() {\r
5264         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth() + this.getScrollOffset()) + 'px';\r
5265     },\r
5266     \r
5267     renderHeaders : function(){\r
5268         var cm = this.cm,\r
5269             ts = this.templates,\r
5270             ct = ts.hcell,\r
5271             cb = [], lcb = [],\r
5272             p = {},\r
5273             len = cm.getColumnCount(),\r
5274             last = len - 1;\r
5275         for(var i = 0; i < len; i++){\r
5276             p.id = cm.getColumnId(i);\r
5277             p.value = cm.getColumnHeader(i) || '';\r
5278             p.style = this.getColumnStyle(i, true);\r
5279             p.tooltip = this.getColumnTooltip(i);\r
5280             p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +\r
5281                 (cm.config[i].headerCls ? ' ' + cm.config[i].headerCls : '');\r
5282             if(cm.config[i].align == 'right'){\r
5283                 p.istyle = 'padding-right:16px';\r
5284             } else {\r
5285                 delete p.istyle;\r
5286             }\r
5287             if(cm.isLocked(i)){\r
5288                 lcb[lcb.length] = ct.apply(p);\r
5289             }else{\r
5290                 cb[cb.length] = ct.apply(p);\r
5291             }\r
5292         }\r
5293         return [ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'}),\r
5294                 ts.header.apply({cells: lcb.join(''), tstyle:'width:'+this.getLockedWidth()+';'})];\r
5295     },\r
5296     \r
5297     updateHeaders : function(){\r
5298         var hd = this.renderHeaders();\r
5299         this.innerHd.firstChild.innerHTML = hd[0];\r
5300         this.innerHd.firstChild.style.width = this.getOffsetWidth();\r
5301         this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();\r
5302         this.lockedInnerHd.firstChild.innerHTML = hd[1];\r
5303         var lw = this.getLockedWidth();\r
5304         this.lockedInnerHd.firstChild.style.width = lw;\r
5305         this.lockedInnerHd.firstChild.firstChild.style.width = lw;\r
5306     },\r
5307     \r
5308     getResolvedXY : function(resolved){\r
5309         if(!resolved){\r
5310             return null;\r
5311         }\r
5312         var c = resolved.cell, r = resolved.row;\r
5313         return c ? Ext.fly(c).getXY() : [this.scroller.getX(), Ext.fly(r).getY()];\r
5314     },\r
5315     \r
5316     syncFocusEl : function(row, col, hscroll){\r
5317         Ext.ux.grid.LockingGridView.superclass.syncFocusEl.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);\r
5318     },\r
5319     \r
5320     ensureVisible : function(row, col, hscroll){\r
5321         return Ext.ux.grid.LockingGridView.superclass.ensureVisible.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);\r
5322     },\r
5323     \r
5324     insertRows : function(dm, firstRow, lastRow, isUpdate){\r
5325         var last = dm.getCount() - 1;\r
5326         if(!isUpdate && firstRow === 0 && lastRow >= last){\r
5327             this.refresh();\r
5328         }else{\r
5329             if(!isUpdate){\r
5330                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);\r
5331             }\r
5332             var html = this.renderRows(firstRow, lastRow),\r
5333                 before = this.getRow(firstRow);\r
5334             if(before){\r
5335                 if(firstRow === 0){\r
5336                     this.removeRowClass(0, this.firstRowCls);\r
5337                 }\r
5338                 Ext.DomHelper.insertHtml('beforeBegin', before, html[0]);\r
5339                 before = this.getLockedRow(firstRow);\r
5340                 Ext.DomHelper.insertHtml('beforeBegin', before, html[1]);\r
5341             }else{\r
5342                 this.removeRowClass(last - 1, this.lastRowCls);\r
5343                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]);\r
5344                 Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]);\r
5345             }\r
5346             if(!isUpdate){\r
5347                 this.fireEvent('rowsinserted', this, firstRow, lastRow);\r
5348                 this.processRows(firstRow);\r
5349             }else if(firstRow === 0 || firstRow >= last){\r
5350                 this.addRowClass(firstRow, firstRow === 0 ? this.firstRowCls : this.lastRowCls);\r
5351             }\r
5352         }\r
5353         this.syncFocusEl(firstRow);\r
5354     },\r
5355     \r
5356     getColumnStyle : function(col, isHeader){\r
5357         var style = !isHeader ? this.cm.config[col].cellStyle || this.cm.config[col].css || '' : this.cm.config[col].headerStyle || '';\r
5358         style += 'width:'+this.getColumnWidth(col)+';';\r
5359         if(this.cm.isHidden(col)){\r
5360             style += 'display:none;';\r
5361         }\r
5362         var align = this.cm.config[col].align;\r
5363         if(align){\r
5364             style += 'text-align:'+align+';';\r
5365         }\r
5366         return style;\r
5367     },\r
5368     \r
5369     getLockedWidth : function() {\r
5370         return this.cm.getTotalLockedWidth() + 'px';\r
5371     },\r
5372     \r
5373     getTotalWidth : function() {\r
5374         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth()) + 'px';\r
5375     },\r
5376     \r
5377     getColumnData : function(){\r
5378         var cs = [], cm = this.cm, colCount = cm.getColumnCount();\r
5379         for(var i = 0; i < colCount; i++){\r
5380             var name = cm.getDataIndex(i);\r
5381             cs[i] = {\r
5382                 name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),\r
5383                 renderer : cm.getRenderer(i),\r
5384                 id : cm.getColumnId(i),\r
5385                 style : this.getColumnStyle(i),\r
5386                 locked : cm.isLocked(i)\r
5387             };\r
5388         }\r
5389         return cs;\r
5390     },\r
5391     \r
5392     renderBody : function(){\r
5393         var markup = this.renderRows() || ['&#160;', '&#160;'];\r
5394         return [this.templates.body.apply({rows: markup[0]}), this.templates.body.apply({rows: markup[1]})];\r
5395     },\r
5396     \r
5397     refreshRow : function(record){\r
5398         Ext.ux.grid.LockingGridView.superclass.refreshRow.call(this, record);\r
5399         var index = Ext.isNumber(record) ? record : this.ds.indexOf(record);\r
5400         this.getLockedRow(index).rowIndex = index;\r
5401     },\r
5402     \r
5403     refresh : function(headersToo){\r
5404         this.fireEvent('beforerefresh', this);\r
5405         this.grid.stopEditing(true);\r
5406         var result = this.renderBody();\r
5407         this.mainBody.update(result[0]).setWidth(this.getTotalWidth());\r
5408         this.lockedBody.update(result[1]).setWidth(this.getLockedWidth());\r
5409         if(headersToo === true){\r
5410             this.updateHeaders();\r
5411             this.updateHeaderSortState();\r
5412         }\r
5413         this.processRows(0, true);\r
5414         this.layout();\r
5415         this.applyEmptyText();\r
5416         this.fireEvent('refresh', this);\r
5417     },\r
5418     \r
5419     onDenyColumnLock : function(){\r
5420 \r
5421     },\r
5422     \r
5423     initData : function(ds, cm){\r
5424         if(this.cm){\r
5425             this.cm.un('columnlockchange', this.onColumnLock, this);\r
5426         }\r
5427         Ext.ux.grid.LockingGridView.superclass.initData.call(this, ds, cm);\r
5428         if(this.cm){\r
5429             this.cm.on('columnlockchange', this.onColumnLock, this);\r
5430         }\r
5431     },\r
5432     \r
5433     onColumnLock : function(){\r
5434         this.refresh(true);\r
5435     },\r
5436     \r
5437     handleHdMenuClick : function(item){\r
5438         var index = this.hdCtxIndex,\r
5439             cm = this.cm,\r
5440             id = item.getItemId(),\r
5441             llen = cm.getLockedCount();\r
5442         switch(id){\r
5443             case 'lock':\r
5444                 if(cm.getColumnCount(true) <= llen + 1){\r
5445                     this.onDenyColumnLock();\r
5446                     return;\r
5447                 }\r
5448                 if(llen != index){\r
5449                     cm.setLocked(index, true, true);\r
5450                     cm.moveColumn(index, llen);\r
5451                     this.grid.fireEvent('columnmove', index, llen);\r
5452                 }else{\r
5453                     cm.setLocked(index, true);\r
5454                 }\r
5455             break;\r
5456             case 'unlock':\r
5457                 if(llen - 1 != index){\r
5458                     cm.setLocked(index, false, true);\r
5459                     cm.moveColumn(index, llen - 1);\r
5460                     this.grid.fireEvent('columnmove', index, llen - 1);\r
5461                 }else{\r
5462                     cm.setLocked(index, false);\r
5463                 }\r
5464             break;\r
5465             default:\r
5466                 return Ext.ux.grid.LockingGridView.superclass.handleHdMenuClick.call(this, item);\r
5467         }\r
5468         return true;\r
5469     },\r
5470     \r
5471     handleHdDown : function(e, t){\r
5472         Ext.ux.grid.LockingGridView.superclass.handleHdDown.call(this, e, t);\r
5473         if(this.grid.enableColLock !== false){\r
5474             if(Ext.fly(t).hasClass('x-grid3-hd-btn')){\r
5475                 var hd = this.findHeaderCell(t),\r
5476                     index = this.getCellIndex(hd),\r
5477                     ms = this.hmenu.items, cm = this.cm;\r
5478                 ms.get('lock').setDisabled(cm.isLocked(index));\r
5479                 ms.get('unlock').setDisabled(!cm.isLocked(index));\r
5480             }\r
5481         }\r
5482     },\r
5483     \r
5484     syncHeaderHeight: function(){\r
5485         this.innerHd.firstChild.firstChild.style.height = 'auto';\r
5486         this.lockedInnerHd.firstChild.firstChild.style.height = 'auto';\r
5487         var hd = this.innerHd.firstChild.firstChild.offsetHeight,\r
5488             lhd = this.lockedInnerHd.firstChild.firstChild.offsetHeight,\r
5489             height = (lhd > hd ? lhd : hd) + 'px';\r
5490         this.innerHd.firstChild.firstChild.style.height = height;\r
5491         this.lockedInnerHd.firstChild.firstChild.style.height = height;\r
5492     },\r
5493     \r
5494     updateLockedWidth: function(){\r
5495         var lw = this.cm.getTotalLockedWidth(),\r
5496             tw = this.cm.getTotalWidth() - lw,\r
5497             csize = this.grid.getGridEl().getSize(true),\r
5498             lp = Ext.isBorderBox ? 0 : this.lockedBorderWidth,\r
5499             rp = Ext.isBorderBox ? 0 : this.rowBorderWidth,\r
5500             vw = (csize.width - lw - lp - rp) + 'px',\r
5501             so = this.getScrollOffset();\r
5502         if(!this.grid.autoHeight){\r
5503             var vh = (csize.height - this.mainHd.getHeight()) + 'px';\r
5504             this.lockedScroller.dom.style.height = vh;\r
5505             this.scroller.dom.style.height = vh;\r
5506         }\r
5507         this.lockedWrap.dom.style.width = (lw + rp) + 'px';\r
5508         this.scroller.dom.style.width = vw;\r
5509         this.mainWrap.dom.style.left = (lw + lp + rp) + 'px';\r
5510         if(this.innerHd){\r
5511             this.lockedInnerHd.firstChild.style.width = lw + 'px';\r
5512             this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px';\r
5513             this.innerHd.style.width = vw;\r
5514             this.innerHd.firstChild.style.width = (tw + rp + so) + 'px';\r
5515             this.innerHd.firstChild.firstChild.style.width = tw + 'px';\r
5516         }\r
5517         if(this.mainBody){\r
5518             this.lockedBody.dom.style.width = (lw + rp) + 'px';\r
5519             this.mainBody.dom.style.width = (tw + rp) + 'px';\r
5520         }\r
5521     }\r
5522 });\r
5523 \r
5524 Ext.ux.grid.LockingColumnModel = Ext.extend(Ext.grid.ColumnModel, {\r
5525     isLocked : function(colIndex){\r
5526         return this.config[colIndex].locked === true;\r
5527     },\r
5528     \r
5529     setLocked : function(colIndex, value, suppressEvent){\r
5530         if(this.isLocked(colIndex) == value){\r
5531             return;\r
5532         }\r
5533         this.config[colIndex].locked = value;\r
5534         if(!suppressEvent){\r
5535             this.fireEvent('columnlockchange', this, colIndex, value);\r
5536         }\r
5537     },\r
5538     \r
5539     getTotalLockedWidth : function(){\r
5540         var totalWidth = 0;\r
5541         for(var i = 0, len = this.config.length; i < len; i++){\r
5542             if(this.isLocked(i) && !this.isHidden(i)){\r
5543                 totalWidth += this.getColumnWidth(i);\r
5544             }\r
5545         }\r
5546         return totalWidth;\r
5547     },\r
5548     \r
5549     getLockedCount : function(){\r
5550         for(var i = 0, len = this.config.length; i < len; i++){\r
5551             if(!this.isLocked(i)){\r
5552                 return i;\r
5553             }\r
5554         }\r
5555     },\r
5556     \r
5557     moveColumn : function(oldIndex, newIndex){\r
5558         if(oldIndex < newIndex && this.isLocked(oldIndex) && !this.isLocked(newIndex)){\r
5559             this.setLocked(oldIndex, false, true);\r
5560         }else if(oldIndex > newIndex && !this.isLocked(oldIndex) && this.isLocked(newIndex)){\r
5561             this.setLocked(oldIndex, true, true);\r
5562         }\r
5563         Ext.ux.grid.LockingColumnModel.superclass.moveColumn.apply(this, arguments);\r
5564     }\r
5565 });\r
5566 Ext.ns('Ext.ux.form');\r
5567 \r
5568 /**\r
5569  * @class Ext.ux.form.MultiSelect\r
5570  * @extends Ext.form.Field\r
5571  * A control that allows selection and form submission of multiple list items.\r
5572  *\r
5573  *  @history\r
5574  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
5575  *    2008-06-19 bpm Docs and demo code clean up\r
5576  *\r
5577  * @constructor\r
5578  * Create a new MultiSelect\r
5579  * @param {Object} config Configuration options\r
5580  * @xtype multiselect \r
5581  */\r
5582 Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {\r
5583     /**\r
5584      * @cfg {String} legend Wraps the object with a fieldset and specified legend.\r
5585      */\r
5586     /**\r
5587      * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.\r
5588      */\r
5589     /**\r
5590      * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).\r
5591      */\r
5592     /**\r
5593      * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).\r
5594      */\r
5595     /**\r
5596      * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).\r
5597      */\r
5598     ddReorder:false,\r
5599     /**\r
5600      * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a\r
5601      * toolbar config, or an array of buttons/button configs to be added to the toolbar.\r
5602      */\r
5603     /**\r
5604      * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled\r
5605      * (use for lists which are sorted, defaults to false).\r
5606      */\r
5607     appendOnly:false,\r
5608     /**\r
5609      * @cfg {Number} width Width in pixels of the control (defaults to 100).\r
5610      */\r
5611     width:100,\r
5612     /**\r
5613      * @cfg {Number} height Height in pixels of the control (defaults to 100).\r
5614      */\r
5615     height:100,\r
5616     /**\r
5617      * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).\r
5618      */\r
5619     displayField:0,\r
5620     /**\r
5621      * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).\r
5622      */\r
5623     valueField:1,\r
5624     /**\r
5625      * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no\r
5626      * selection (defaults to true).\r
5627      */\r
5628     allowBlank:true,\r
5629     /**\r
5630      * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).\r
5631      */\r
5632     minSelections:0,\r
5633     /**\r
5634      * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).\r
5635      */\r
5636     maxSelections:Number.MAX_VALUE,\r
5637     /**\r
5638      * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as\r
5639      * {@link Ext.form.TextField#blankText}.\r
5640      */\r
5641     blankText:Ext.form.TextField.prototype.blankText,\r
5642     /**\r
5643      * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}\r
5644      * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.\r
5645      */\r
5646     minSelectionsText:'Minimum {0} item(s) required',\r
5647     /**\r
5648      * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}\r
5649      * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.\r
5650      */\r
5651     maxSelectionsText:'Maximum {0} item(s) allowed',\r
5652     /**\r
5653      * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values\r
5654      * (defaults to ',').\r
5655      */\r
5656     delimiter:',',\r
5657     /**\r
5658      * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).\r
5659      * Acceptable values for this property are:\r
5660      * <div class="mdetail-params"><ul>\r
5661      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>\r
5662      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.\r
5663      * <div class="mdetail-params"><ul>\r
5664      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">\r
5665      * A 1-dimensional array will automatically be expanded (each array item will be the combo\r
5666      * {@link #valueField value} and {@link #displayField text})</div></li>\r
5667      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">\r
5668      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo\r
5669      * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.\r
5670      * </div></li></ul></div></li></ul></div>\r
5671      */\r
5672 \r
5673     // private\r
5674     defaultAutoCreate : {tag: "div"},\r
5675 \r
5676     // private\r
5677     initComponent: function(){\r
5678         Ext.ux.form.MultiSelect.superclass.initComponent.call(this);\r
5679 \r
5680         if(Ext.isArray(this.store)){\r
5681             if (Ext.isArray(this.store[0])){\r
5682                 this.store = new Ext.data.ArrayStore({\r
5683                     fields: ['value','text'],\r
5684                     data: this.store\r
5685                 });\r
5686                 this.valueField = 'value';\r
5687             }else{\r
5688                 this.store = new Ext.data.ArrayStore({\r
5689                     fields: ['text'],\r
5690                     data: this.store,\r
5691                     expandData: true\r
5692                 });\r
5693                 this.valueField = 'text';\r
5694             }\r
5695             this.displayField = 'text';\r
5696         } else {\r
5697             this.store = Ext.StoreMgr.lookup(this.store);\r
5698         }\r
5699 \r
5700         this.addEvents({\r
5701             'dblclick' : true,\r
5702             'click' : true,\r
5703             'change' : true,\r
5704             'drop' : true\r
5705         });\r
5706     },\r
5707 \r
5708     // private\r
5709     onRender: function(ct, position){\r
5710         Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);\r
5711 \r
5712         var fs = this.fs = new Ext.form.FieldSet({\r
5713             renderTo: this.el,\r
5714             title: this.legend,\r
5715             height: this.height,\r
5716             width: this.width,\r
5717             style: "padding:0;",\r
5718             tbar: this.tbar\r
5719         });\r
5720         fs.body.addClass('ux-mselect');\r
5721 \r
5722         this.view = new Ext.ListView({\r
5723             multiSelect: true,\r
5724             store: this.store,\r
5725             columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],\r
5726             hideHeaders: true\r
5727         });\r
5728 \r
5729         fs.add(this.view);\r
5730 \r
5731         this.view.on('click', this.onViewClick, this);\r
5732         this.view.on('beforeclick', this.onViewBeforeClick, this);\r
5733         this.view.on('dblclick', this.onViewDblClick, this);\r
5734 \r
5735         this.hiddenName = this.name || Ext.id();\r
5736         var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };\r
5737         this.hiddenField = this.el.createChild(hiddenTag);\r
5738         this.hiddenField.dom.disabled = this.hiddenName != this.name;\r
5739         fs.doLayout();\r
5740     },\r
5741 \r
5742     // private\r
5743     afterRender: function(){\r
5744         Ext.ux.form.MultiSelect.superclass.afterRender.call(this);\r
5745 \r
5746         if (this.ddReorder && !this.dragGroup && !this.dropGroup){\r
5747             this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();\r
5748         }\r
5749 \r
5750         if (this.draggable || this.dragGroup){\r
5751             this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {\r
5752                 ddGroup: this.dragGroup\r
5753             });\r
5754         }\r
5755         if (this.droppable || this.dropGroup){\r
5756             this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {\r
5757                 ddGroup: this.dropGroup\r
5758             });\r
5759         }\r
5760     },\r
5761 \r
5762     // private\r
5763     onViewClick: function(vw, index, node, e) {\r
5764         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
5765         this.hiddenField.dom.value = this.getValue();\r
5766         this.fireEvent('click', this, e);\r
5767         this.validate();\r
5768     },\r
5769 \r
5770     // private\r
5771     onViewBeforeClick: function(vw, index, node, e) {\r
5772         if (this.disabled) {return false;}\r
5773     },\r
5774 \r
5775     // private\r
5776     onViewDblClick : function(vw, index, node, e) {\r
5777         return this.fireEvent('dblclick', vw, index, node, e);\r
5778     },\r
5779 \r
5780     /**\r
5781      * Returns an array of data values for the selected items in the list. The values will be separated\r
5782      * by {@link #delimiter}.\r
5783      * @return {Array} value An array of string data values\r
5784      */\r
5785     getValue: function(valueField){\r
5786         var returnArray = [];\r
5787         var selectionsArray = this.view.getSelectedIndexes();\r
5788         if (selectionsArray.length == 0) {return '';}\r
5789         for (var i=0; i<selectionsArray.length; i++) {\r
5790             returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));\r
5791         }\r
5792         return returnArray.join(this.delimiter);\r
5793     },\r
5794 \r
5795     /**\r
5796      * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.\r
5797      * @param {String/Array} values The values to set\r
5798      */\r
5799     setValue: function(values) {\r
5800         var index;\r
5801         var selections = [];\r
5802         this.view.clearSelections();\r
5803         this.hiddenField.dom.value = '';\r
5804 \r
5805         if (!values || (values == '')) { return; }\r
5806 \r
5807         if (!Ext.isArray(values)) { values = values.split(this.delimiter); }\r
5808         for (var i=0; i<values.length; i++) {\r
5809             index = this.view.store.indexOf(this.view.store.query(this.valueField,\r
5810                 new RegExp('^' + values[i] + '$', "i")).itemAt(0));\r
5811             selections.push(index);\r
5812         }\r
5813         this.view.select(selections);\r
5814         this.hiddenField.dom.value = this.getValue();\r
5815         this.validate();\r
5816     },\r
5817 \r
5818     // inherit docs\r
5819     reset : function() {\r
5820         this.setValue('');\r
5821     },\r
5822 \r
5823     // inherit docs\r
5824     getRawValue: function(valueField) {\r
5825         var tmp = this.getValue(valueField);\r
5826         if (tmp.length) {\r
5827             tmp = tmp.split(this.delimiter);\r
5828         }\r
5829         else {\r
5830             tmp = [];\r
5831         }\r
5832         return tmp;\r
5833     },\r
5834 \r
5835     // inherit docs\r
5836     setRawValue: function(values){\r
5837         setValue(values);\r
5838     },\r
5839 \r
5840     // inherit docs\r
5841     validateValue : function(value){\r
5842         if (value.length < 1) { // if it has no value\r
5843              if (this.allowBlank) {\r
5844                  this.clearInvalid();\r
5845                  return true;\r
5846              } else {\r
5847                  this.markInvalid(this.blankText);\r
5848                  return false;\r
5849              }\r
5850         }\r
5851         if (value.length < this.minSelections) {\r
5852             this.markInvalid(String.format(this.minSelectionsText, this.minSelections));\r
5853             return false;\r
5854         }\r
5855         if (value.length > this.maxSelections) {\r
5856             this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));\r
5857             return false;\r
5858         }\r
5859         return true;\r
5860     },\r
5861 \r
5862     // inherit docs\r
5863     disable: function(){\r
5864         this.disabled = true;\r
5865         this.hiddenField.dom.disabled = true;\r
5866         this.fs.disable();\r
5867     },\r
5868 \r
5869     // inherit docs\r
5870     enable: function(){\r
5871         this.disabled = false;\r
5872         this.hiddenField.dom.disabled = false;\r
5873         this.fs.enable();\r
5874     },\r
5875 \r
5876     // inherit docs\r
5877     destroy: function(){\r
5878         Ext.destroy(this.fs, this.dragZone, this.dropZone);\r
5879         Ext.ux.form.MultiSelect.superclass.destroy.call(this);\r
5880     }\r
5881 });\r
5882 \r
5883 \r
5884 Ext.reg('multiselect', Ext.ux.form.MultiSelect);\r
5885 \r
5886 //backwards compat\r
5887 Ext.ux.Multiselect = Ext.ux.form.MultiSelect;\r
5888 \r
5889 \r
5890 Ext.ux.form.MultiSelect.DragZone = function(ms, config){\r
5891     this.ms = ms;\r
5892     this.view = ms.view;\r
5893     var ddGroup = config.ddGroup || 'MultiselectDD';\r
5894     var dd;\r
5895     if (Ext.isArray(ddGroup)){\r
5896         dd = ddGroup.shift();\r
5897     } else {\r
5898         dd = ddGroup;\r
5899         ddGroup = null;\r
5900     }\r
5901     Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
5902     this.setDraggable(ddGroup);\r
5903 };\r
5904 \r
5905 Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {\r
5906     onInitDrag : function(x, y){\r
5907         var el = Ext.get(this.dragData.ddel.cloneNode(true));\r
5908         this.proxy.update(el.dom);\r
5909         el.setWidth(el.child('em').getWidth());\r
5910         this.onStartDrag(x, y);\r
5911         return true;\r
5912     },\r
5913     \r
5914     // private\r
5915     collectSelection: function(data) {\r
5916         data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();\r
5917         var i = 0;\r
5918         this.view.store.each(function(rec){\r
5919             if (this.view.isSelected(i)) {\r
5920                 var n = this.view.getNode(i);\r
5921                 var dragNode = n.cloneNode(true);\r
5922                 dragNode.id = Ext.id();\r
5923                 data.ddel.appendChild(dragNode);\r
5924                 data.records.push(this.view.store.getAt(i));\r
5925                 data.viewNodes.push(n);\r
5926             }\r
5927             i++;\r
5928         }, this);\r
5929     },\r
5930 \r
5931     // override\r
5932     onEndDrag: function(data, e) {\r
5933         var d = Ext.get(this.dragData.ddel);\r
5934         if (d && d.hasClass("multi-proxy")) {\r
5935             d.remove();\r
5936         }\r
5937     },\r
5938 \r
5939     // override\r
5940     getDragData: function(e){\r
5941         var target = this.view.findItemFromChild(e.getTarget());\r
5942         if(target) {\r
5943             if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {\r
5944                 this.view.select(target);\r
5945                 this.ms.setValue(this.ms.getValue());\r
5946             }\r
5947             if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;\r
5948             var dragData = {\r
5949                 sourceView: this.view,\r
5950                 viewNodes: [],\r
5951                 records: []\r
5952             };\r
5953             if (this.view.getSelectionCount() == 1) {\r
5954                 var i = this.view.getSelectedIndexes()[0];\r
5955                 var n = this.view.getNode(i);\r
5956                 dragData.viewNodes.push(dragData.ddel = n);\r
5957                 dragData.records.push(this.view.store.getAt(i));\r
5958                 dragData.repairXY = Ext.fly(n).getXY();\r
5959             } else {\r
5960                 dragData.ddel = document.createElement('div');\r
5961                 dragData.ddel.className = 'multi-proxy';\r
5962                 this.collectSelection(dragData);\r
5963             }\r
5964             return dragData;\r
5965         }\r
5966         return false;\r
5967     },\r
5968 \r
5969     // override the default repairXY.\r
5970     getRepairXY : function(e){\r
5971         return this.dragData.repairXY;\r
5972     },\r
5973 \r
5974     // private\r
5975     setDraggable: function(ddGroup){\r
5976         if (!ddGroup) return;\r
5977         if (Ext.isArray(ddGroup)) {\r
5978             Ext.each(ddGroup, this.setDraggable, this);\r
5979             return;\r
5980         }\r
5981         this.addToGroup(ddGroup);\r
5982     }\r
5983 });\r
5984 \r
5985 Ext.ux.form.MultiSelect.DropZone = function(ms, config){\r
5986     this.ms = ms;\r
5987     this.view = ms.view;\r
5988     var ddGroup = config.ddGroup || 'MultiselectDD';\r
5989     var dd;\r
5990     if (Ext.isArray(ddGroup)){\r
5991         dd = ddGroup.shift();\r
5992     } else {\r
5993         dd = ddGroup;\r
5994         ddGroup = null;\r
5995     }\r
5996     Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
5997     this.setDroppable(ddGroup);\r
5998 };\r
5999 \r
6000 Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {\r
6001     /**\r
6002          * Part of the Ext.dd.DropZone interface. If no target node is found, the\r
6003          * whole Element becomes the target, and this causes the drop gesture to append.\r
6004          */\r
6005     getTargetFromEvent : function(e) {\r
6006         var target = e.getTarget();\r
6007         return target;\r
6008     },\r
6009 \r
6010     // private\r
6011     getDropPoint : function(e, n, dd){\r
6012         if (n == this.ms.fs.body.dom) { return "below"; }\r
6013         var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;\r
6014         var c = t + (b - t) / 2;\r
6015         var y = Ext.lib.Event.getPageY(e);\r
6016         if(y <= c) {\r
6017             return "above";\r
6018         }else{\r
6019             return "below";\r
6020         }\r
6021     },\r
6022 \r
6023     // private\r
6024     isValidDropPoint: function(pt, n, data) {\r
6025         if (!data.viewNodes || (data.viewNodes.length != 1)) {\r
6026             return true;\r
6027         }\r
6028         var d = data.viewNodes[0];\r
6029         if (d == n) {\r
6030             return false;\r
6031         }\r
6032         if ((pt == "below") && (n.nextSibling == d)) {\r
6033             return false;\r
6034         }\r
6035         if ((pt == "above") && (n.previousSibling == d)) {\r
6036             return false;\r
6037         }\r
6038         return true;\r
6039     },\r
6040 \r
6041     // override\r
6042     onNodeEnter : function(n, dd, e, data){\r
6043         return false;\r
6044     },\r
6045 \r
6046     // override\r
6047     onNodeOver : function(n, dd, e, data){\r
6048         var dragElClass = this.dropNotAllowed;\r
6049         var pt = this.getDropPoint(e, n, dd);\r
6050         if (this.isValidDropPoint(pt, n, data)) {\r
6051             if (this.ms.appendOnly) {\r
6052                 return "x-tree-drop-ok-below";\r
6053             }\r
6054 \r
6055             // set the insert point style on the target node\r
6056             if (pt) {\r
6057                 var targetElClass;\r
6058                 if (pt == "above"){\r
6059                     dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";\r
6060                     targetElClass = "x-view-drag-insert-above";\r
6061                 } else {\r
6062                     dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";\r
6063                     targetElClass = "x-view-drag-insert-below";\r
6064                 }\r
6065                 if (this.lastInsertClass != targetElClass){\r
6066                     Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);\r
6067                     this.lastInsertClass = targetElClass;\r
6068                 }\r
6069             }\r
6070         }\r
6071         return dragElClass;\r
6072     },\r
6073 \r
6074     // private\r
6075     onNodeOut : function(n, dd, e, data){\r
6076         this.removeDropIndicators(n);\r
6077     },\r
6078 \r
6079     // private\r
6080     onNodeDrop : function(n, dd, e, data){\r
6081         if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {\r
6082             return false;\r
6083         }\r
6084         var pt = this.getDropPoint(e, n, dd);\r
6085         if (n != this.ms.fs.body.dom)\r
6086             n = this.view.findItemFromChild(n);\r
6087         var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n);\r
6088         if (pt == "below") {\r
6089             insertAt++;\r
6090         }\r
6091 \r
6092         var dir = false;\r
6093 \r
6094         // Validate if dragging within the same MultiSelect\r
6095         if (data.sourceView == this.view) {\r
6096             // If the first element to be inserted below is the target node, remove it\r
6097             if (pt == "below") {\r
6098                 if (data.viewNodes[0] == n) {\r
6099                     data.viewNodes.shift();\r
6100                 }\r
6101             } else {  // If the last element to be inserted above is the target node, remove it\r
6102                 if (data.viewNodes[data.viewNodes.length - 1] == n) {\r
6103                     data.viewNodes.pop();\r
6104                 }\r
6105             }\r
6106 \r
6107             // Nothing to drop...\r
6108             if (!data.viewNodes.length) {\r
6109                 return false;\r
6110             }\r
6111 \r
6112             // If we are moving DOWN, then because a store.remove() takes place first,\r
6113             // the insertAt must be decremented.\r
6114             if (insertAt > this.view.store.indexOf(data.records[0])) {\r
6115                 dir = 'down';\r
6116                 insertAt--;\r
6117             }\r
6118         }\r
6119 \r
6120         for (var i = 0; i < data.records.length; i++) {\r
6121             var r = data.records[i];\r
6122             if (data.sourceView) {\r
6123                 data.sourceView.store.remove(r);\r
6124             }\r
6125             this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);\r
6126             var si = this.view.store.sortInfo;\r
6127             if(si){\r
6128                 this.view.store.sort(si.field, si.direction);\r
6129             }\r
6130         }\r
6131         return true;\r
6132     },\r
6133 \r
6134     // private\r
6135     removeDropIndicators : function(n){\r
6136         if(n){\r
6137             Ext.fly(n).removeClass([\r
6138                 "x-view-drag-insert-above",\r
6139                 "x-view-drag-insert-left",\r
6140                 "x-view-drag-insert-right",\r
6141                 "x-view-drag-insert-below"]);\r
6142             this.lastInsertClass = "_noclass";\r
6143         }\r
6144     },\r
6145 \r
6146     // private\r
6147     setDroppable: function(ddGroup){\r
6148         if (!ddGroup) return;\r
6149         if (Ext.isArray(ddGroup)) {\r
6150             Ext.each(ddGroup, this.setDroppable, this);\r
6151             return;\r
6152         }\r
6153         this.addToGroup(ddGroup);\r
6154     }\r
6155 });\r
6156
6157 /* Fix for Opera, which does not seem to include the map function on Array's */
6158 if (!Array.prototype.map) {
6159     Array.prototype.map = function(fun){
6160         var len = this.length;
6161         if (typeof fun != 'function') {
6162             throw new TypeError();
6163         }
6164         var res = new Array(len);
6165         var thisp = arguments[1];
6166         for (var i = 0; i < len; i++) {
6167             if (i in this) {
6168                 res[i] = fun.call(thisp, this[i], i, this);
6169             }
6170         }
6171         return res;
6172     };
6173 }
6174
6175 Ext.ns('Ext.ux.data');
6176
6177 /**
6178  * @class Ext.ux.data.PagingMemoryProxy
6179  * @extends Ext.data.MemoryProxy
6180  * <p>Paging Memory Proxy, allows to use paging grid with in memory dataset</p>
6181  */
6182 Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, {
6183     constructor : function(data){
6184         Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this);
6185         this.data = data;
6186     },
6187     doRequest : function(action, rs, params, reader, callback, scope, options){
6188         params = params ||
6189         {};
6190         var result;
6191         try {
6192             result = reader.readRecords(this.data);
6193         } 
6194         catch (e) {
6195             this.fireEvent('loadexception', this, options, null, e);
6196             callback.call(scope, null, options, false);
6197             return;
6198         }
6199         
6200         // filtering
6201         if (params.filter !== undefined) {
6202             result.records = result.records.filter(function(el){
6203                 if (typeof(el) == 'object') {
6204                     var att = params.filterCol || 0;
6205                     return String(el.data[att]).match(params.filter) ? true : false;
6206                 }
6207                 else {
6208                     return String(el).match(params.filter) ? true : false;
6209                 }
6210             });
6211             result.totalRecords = result.records.length;
6212         }
6213         
6214         // sorting
6215         if (params.sort !== undefined) {
6216             // use integer as params.sort to specify column, since arrays are not named
6217             // params.sort=0; would also match a array without columns
6218             var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1;
6219             var fn = function(v1, v2){
6220                 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
6221             };
6222             result.records.sort(function(a, b){
6223                 var v = 0;
6224                 if (typeof(a) == 'object') {
6225                     v = fn(a.data[params.sort], b.data[params.sort]) * dir;
6226                 }
6227                 else {
6228                     v = fn(a, b) * dir;
6229                 }
6230                 if (v == 0) {
6231                     v = (a.index < b.index ? -1 : 1);
6232                 }
6233                 return v;
6234             });
6235         }
6236         // paging (use undefined cause start can also be 0 (thus false))
6237         if (params.start !== undefined && params.limit !== undefined) {
6238             result.records = result.records.slice(params.start, params.start + params.limit);
6239         }
6240         callback.call(scope, result, options, true);
6241     }
6242 });
6243
6244 //backwards compat.
6245 Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
6246 Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {\r
6247     minHeight: 0,\r
6248     maxHeight:10000000,\r
6249 \r
6250     constructor: function(config){\r
6251         Ext.apply(this, config);\r
6252         this.events = {};\r
6253         Ext.ux.PanelResizer.superclass.constructor.call(this, config);\r
6254     },\r
6255 \r
6256     init : function(p){\r
6257         this.panel = p;\r
6258 \r
6259         if(this.panel.elements.indexOf('footer')==-1){\r
6260             p.elements += ',footer';\r
6261         }\r
6262         p.on('render', this.onRender, this);\r
6263     },\r
6264 \r
6265     onRender : function(p){\r
6266         this.handle = p.footer.createChild({cls:'x-panel-resize'});\r
6267 \r
6268         this.tracker = new Ext.dd.DragTracker({\r
6269             onStart: this.onDragStart.createDelegate(this),\r
6270             onDrag: this.onDrag.createDelegate(this),\r
6271             onEnd: this.onDragEnd.createDelegate(this),\r
6272             tolerance: 3,\r
6273             autoStart: 300\r
6274         });\r
6275         this.tracker.initEl(this.handle);\r
6276         p.on('beforedestroy', this.tracker.destroy, this.tracker);\r
6277     },\r
6278 \r
6279         // private\r
6280     onDragStart: function(e){\r
6281         this.dragging = true;\r
6282         this.startHeight = this.panel.el.getHeight();\r
6283         this.fireEvent('dragstart', this, e);\r
6284     },\r
6285 \r
6286         // private\r
6287     onDrag: function(e){\r
6288         this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));\r
6289         this.fireEvent('drag', this, e);\r
6290     },\r
6291 \r
6292         // private\r
6293     onDragEnd: function(e){\r
6294         this.dragging = false;\r
6295         this.fireEvent('dragend', this, e);\r
6296     }\r
6297 });\r
6298 Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {\r
6299     layout : 'column',\r
6300     autoScroll : true,\r
6301     cls : 'x-portal',\r
6302     defaultType : 'portalcolumn',\r
6303     \r
6304     initComponent : function(){\r
6305         Ext.ux.Portal.superclass.initComponent.call(this);\r
6306         this.addEvents({\r
6307             validatedrop:true,\r
6308             beforedragover:true,\r
6309             dragover:true,\r
6310             beforedrop:true,\r
6311             drop:true\r
6312         });\r
6313     },\r
6314 \r
6315     initEvents : function(){\r
6316         Ext.ux.Portal.superclass.initEvents.call(this);\r
6317         this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);\r
6318     },\r
6319     \r
6320     beforeDestroy : function() {\r
6321         if(this.dd){\r
6322             this.dd.unreg();\r
6323         }\r
6324         Ext.ux.Portal.superclass.beforeDestroy.call(this);\r
6325     }\r
6326 });\r
6327 \r
6328 Ext.reg('portal', Ext.ux.Portal);\r
6329 \r
6330 \r
6331 Ext.ux.Portal.DropZone = function(portal, cfg){\r
6332     this.portal = portal;\r
6333     Ext.dd.ScrollManager.register(portal.body);\r
6334     Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);\r
6335     portal.body.ddScrollConfig = this.ddScrollConfig;\r
6336 };\r
6337 \r
6338 Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {\r
6339     ddScrollConfig : {\r
6340         vthresh: 50,\r
6341         hthresh: -1,\r
6342         animate: true,\r
6343         increment: 200\r
6344     },\r
6345 \r
6346     createEvent : function(dd, e, data, col, c, pos){\r
6347         return {\r
6348             portal: this.portal,\r
6349             panel: data.panel,\r
6350             columnIndex: col,\r
6351             column: c,\r
6352             position: pos,\r
6353             data: data,\r
6354             source: dd,\r
6355             rawEvent: e,\r
6356             status: this.dropAllowed\r
6357         };\r
6358     },\r
6359 \r
6360     notifyOver : function(dd, e, data){\r
6361         var xy = e.getXY(), portal = this.portal, px = dd.proxy;\r
6362 \r
6363         // case column widths\r
6364         if(!this.grid){\r
6365             this.grid = this.getGrid();\r
6366         }\r
6367 \r
6368         // handle case scroll where scrollbars appear during drag\r
6369         var cw = portal.body.dom.clientWidth;\r
6370         if(!this.lastCW){\r
6371             this.lastCW = cw;\r
6372         }else if(this.lastCW != cw){\r
6373             this.lastCW = cw;\r
6374             portal.doLayout();\r
6375             this.grid = this.getGrid();\r
6376         }\r
6377 \r
6378         // determine column\r
6379         var col = 0, xs = this.grid.columnX, cmatch = false;\r
6380         for(var len = xs.length; col < len; col++){\r
6381             if(xy[0] < (xs[col].x + xs[col].w)){\r
6382                 cmatch = true;\r
6383                 break;\r
6384             }\r
6385         }\r
6386         // no match, fix last index\r
6387         if(!cmatch){\r
6388             col--;\r
6389         }\r
6390 \r
6391         // find insert position\r
6392         var p, match = false, pos = 0,\r
6393             c = portal.items.itemAt(col),\r
6394             items = c.items.items, overSelf = false;\r
6395 \r
6396         for(var len = items.length; pos < len; pos++){\r
6397             p = items[pos];\r
6398             var h = p.el.getHeight();\r
6399             if(h === 0){\r
6400                 overSelf = true;\r
6401             }\r
6402             else if((p.el.getY()+(h/2)) > xy[1]){\r
6403                 match = true;\r
6404                 break;\r
6405             }\r
6406         }\r
6407 \r
6408         pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);\r
6409         var overEvent = this.createEvent(dd, e, data, col, c, pos);\r
6410 \r
6411         if(portal.fireEvent('validatedrop', overEvent) !== false &&\r
6412            portal.fireEvent('beforedragover', overEvent) !== false){\r
6413 \r
6414             // make sure proxy width is fluid\r
6415             px.getProxy().setWidth('auto');\r
6416 \r
6417             if(p){\r
6418                 px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);\r
6419             }else{\r
6420                 px.moveProxy(c.el.dom, null);\r
6421             }\r
6422 \r
6423             this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};\r
6424             this.scrollPos = portal.body.getScroll();\r
6425 \r
6426             portal.fireEvent('dragover', overEvent);\r
6427 \r
6428             return overEvent.status;\r
6429         }else{\r
6430             return overEvent.status;\r
6431         }\r
6432 \r
6433     },\r
6434 \r
6435     notifyOut : function(){\r
6436         delete this.grid;\r
6437     },\r
6438 \r
6439     notifyDrop : function(dd, e, data){\r
6440         delete this.grid;\r
6441         if(!this.lastPos){\r
6442             return;\r
6443         }\r
6444         var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;\r
6445 \r
6446         var dropEvent = this.createEvent(dd, e, data, col, c,\r
6447             pos !== false ? pos : c.items.getCount());\r
6448 \r
6449         if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&\r
6450            this.portal.fireEvent('beforedrop', dropEvent) !== false){\r
6451 \r
6452             dd.proxy.getProxy().remove();\r
6453             dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);\r
6454             \r
6455             if(pos !== false){\r
6456                 if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){\r
6457                     pos++;\r
6458                 }\r
6459                 c.insert(pos, dd.panel);\r
6460             }else{\r
6461                 c.add(dd.panel);\r
6462             }\r
6463             \r
6464             c.doLayout();\r
6465 \r
6466             this.portal.fireEvent('drop', dropEvent);\r
6467 \r
6468             // scroll position is lost on drop, fix it\r
6469             var st = this.scrollPos.top;\r
6470             if(st){\r
6471                 var d = this.portal.body.dom;\r
6472                 setTimeout(function(){\r
6473                     d.scrollTop = st;\r
6474                 }, 10);\r
6475             }\r
6476 \r
6477         }\r
6478         delete this.lastPos;\r
6479     },\r
6480 \r
6481     // internal cache of body and column coords\r
6482     getGrid : function(){\r
6483         var box = this.portal.bwrap.getBox();\r
6484         box.columnX = [];\r
6485         this.portal.items.each(function(c){\r
6486              box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});\r
6487         });\r
6488         return box;\r
6489     },\r
6490 \r
6491     // unregister the dropzone from ScrollManager\r
6492     unreg: function() {\r
6493         //Ext.dd.ScrollManager.unregister(this.portal.body);\r
6494         Ext.ux.Portal.DropZone.superclass.unreg.call(this);\r
6495     }\r
6496 });\r
6497 Ext.ux.PortalColumn = Ext.extend(Ext.Container, {\r
6498     layout : 'anchor',\r
6499     //autoEl : 'div',//already defined by Ext.Component\r
6500     defaultType : 'portlet',\r
6501     cls : 'x-portal-column'\r
6502 });\r
6503 \r
6504 Ext.reg('portalcolumn', Ext.ux.PortalColumn);\r
6505 Ext.ux.Portlet = Ext.extend(Ext.Panel, {\r
6506     anchor : '100%',\r
6507     frame : true,\r
6508     collapsible : true,\r
6509     draggable : true,\r
6510     cls : 'x-portlet'\r
6511 });\r
6512 \r
6513 Ext.reg('portlet', Ext.ux.Portlet);\r
6514 /**
6515 * @class Ext.ux.ProgressBarPager
6516 * @extends Object 
6517 * Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text
6518
6519 * @ptype progressbarpager 
6520 * @constructor
6521 * Create a new ItemSelector
6522 * @param {Object} config Configuration options
6523 * @xtype itemselector 
6524 */
6525 Ext.ux.ProgressBarPager  = Ext.extend(Object, {
6526         /**
6527         * @cfg {Integer} progBarWidth
6528         * <p>The default progress bar width.  Default is 225.</p>
6529         */
6530         progBarWidth   : 225,
6531         /**
6532         * @cfg {String} defaultText
6533         * <p>The text to display while the store is loading.  Default is 'Loading...'</p>
6534         */
6535         defaultText    : 'Loading...',
6536         /**
6537         * @cfg {Object} defaultAnimCfg 
6538         * <p>A {@link Ext.Fx Ext.Fx} configuration object.  Default is  { duration : 1, easing : 'bounceOut' }.</p>
6539         */
6540         defaultAnimCfg : {
6541                 duration   : 1,
6542                 easing     : 'bounceOut'        
6543         },                                                                                                
6544         constructor : function(config) {
6545                 if (config) {
6546                         Ext.apply(this, config);
6547                 }
6548         },
6549         //public
6550         init : function (parent) {
6551                 
6552                 if(parent.displayInfo){
6553                         this.parent = parent;
6554                         var ind  = parent.items.indexOf(parent.displayItem);
6555                         parent.remove(parent.displayItem, true);
6556                         this.progressBar = new Ext.ProgressBar({
6557                                 text    : this.defaultText,
6558                                 width   : this.progBarWidth,
6559                                 animate :  this.defaultAnimCfg
6560                         });                                     
6561                    
6562                         parent.displayItem = this.progressBar;
6563                         
6564                         parent.add(parent.displayItem); 
6565                         parent.doLayout();
6566                         Ext.apply(parent, this.parentOverrides);                
6567                         
6568                         this.progressBar.on('render', function(pb) {
6569                 pb.mon(pb.getEl().applyStyles('cursor:pointer'), 'click', this.handleProgressBarClick, this);
6570             }, this, {single: true});
6571                                                 
6572                 }
6573                   
6574         },
6575         // private
6576         // This method handles the click for the progress bar
6577         handleProgressBarClick : function(e){
6578                 var parent = this.parent,
6579                     displayItem = parent.displayItem,
6580                     box = this.progressBar.getBox(),
6581                     xy = e.getXY(),
6582                     position = xy[0]-box.x,
6583                     pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize),
6584                     newpage = Math.ceil(position/(displayItem.width/pages));
6585             
6586                 parent.changePage(newpage);
6587         },
6588         
6589         // private, overriddes
6590         parentOverrides  : {
6591                 // private
6592                 // This method updates the information via the progress bar.
6593                 updateInfo : function(){
6594                         if(this.displayItem){
6595                                 var count = this.store.getCount(),
6596                                     pgData = this.getPageData(),
6597                                     pageNum = this.readPage(pgData),
6598                                     msg = count == 0 ?
6599                                         this.emptyMsg :
6600                                         String.format(
6601                                                 this.displayMsg,
6602                                                 this.cursor+1, this.cursor+count, this.store.getTotalCount()
6603                                         );
6604                                         
6605                                 pageNum = pgData.activePage; ;  
6606                                 
6607                                 var pct = pageNum / pgData.pages;       
6608                                 
6609                                 this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig);
6610                         }
6611                 }
6612         }
6613 });
6614 Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
6615
6616 Ext.ns('Ext.ux.grid');
6617
6618 /**
6619  * @class Ext.ux.grid.RowEditor
6620  * @extends Ext.Panel 
6621  * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
6622  * A validation mode may be enabled which uses AnchorTips to notify the user of all
6623  * validation errors at once.
6624  * 
6625  * @ptype roweditor
6626  */
6627 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
6628     floating: true,
6629     shadow: false,
6630     layout: 'hbox',
6631     cls: 'x-small-editor',
6632     buttonAlign: 'center',
6633     baseCls: 'x-row-editor',
6634     elements: 'header,footer,body',
6635     frameWidth: 5,
6636     buttonPad: 3,
6637     clicksToEdit: 'auto',
6638     monitorValid: true,
6639     focusDelay: 250,
6640     errorSummary: true,
6641     
6642     saveText: 'Save',
6643     cancelText: 'Cancel',
6644     commitChangesText: 'You need to commit or cancel your changes',
6645     errorText: 'Errors',
6646
6647     defaults: {
6648         normalWidth: true
6649     },
6650
6651     initComponent: function(){
6652         Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
6653         this.addEvents(
6654             /**
6655              * @event beforeedit
6656              * Fired before the row editor is activated.
6657              * If the listener returns <tt>false</tt> the editor will not be activated.
6658              * @param {Ext.ux.grid.RowEditor} roweditor This object
6659              * @param {Number} rowIndex The rowIndex of the row just edited
6660              */
6661             'beforeedit',
6662             /**
6663              * @event canceledit
6664              * Fired when the editor is cancelled.
6665              * @param {Ext.ux.grid.RowEditor} roweditor This object
6666              * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid. 
6667              */
6668             'canceledit',
6669             /**
6670              * @event validateedit
6671              * Fired after a row is edited and passes validation.
6672              * If the listener returns <tt>false</tt> changes to the record will not be set.
6673              * @param {Ext.ux.grid.RowEditor} roweditor This object
6674              * @param {Object} changes Object with changes made to the record.
6675              * @param {Ext.data.Record} r The Record that was edited.
6676              * @param {Number} rowIndex The rowIndex of the row just edited
6677              */
6678             'validateedit',
6679             /**
6680              * @event afteredit
6681              * Fired after a row is edited and passes validation.  This event is fired
6682              * after the store's update event is fired with this edit.
6683              * @param {Ext.ux.grid.RowEditor} roweditor This object
6684              * @param {Object} changes Object with changes made to the record.
6685              * @param {Ext.data.Record} r The Record that was edited.
6686              * @param {Number} rowIndex The rowIndex of the row just edited
6687              */
6688             'afteredit'
6689         );
6690     },
6691
6692     init: function(grid){
6693         this.grid = grid;
6694         this.ownerCt = grid;
6695         if(this.clicksToEdit === 2){
6696             grid.on('rowdblclick', this.onRowDblClick, this);
6697         }else{
6698             grid.on('rowclick', this.onRowClick, this);
6699             if(Ext.isIE){
6700                 grid.on('rowdblclick', this.onRowDblClick, this);
6701             }
6702         }
6703
6704         // stopEditing without saving when a record is removed from Store.
6705         grid.getStore().on('remove', function() {
6706             this.stopEditing(false);
6707         },this);
6708
6709         grid.on({
6710             scope: this,
6711             keydown: this.onGridKey,
6712             columnresize: this.verifyLayout,
6713             columnmove: this.refreshFields,
6714             reconfigure: this.refreshFields,
6715                 beforedestroy : this.beforedestroy,
6716                 destroy : this.destroy,
6717             bodyscroll: {
6718                 buffer: 250,
6719                 fn: this.positionButtons
6720             }
6721         });
6722         grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
6723         grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
6724     },
6725
6726     beforedestroy: function() {
6727         this.grid.getStore().un('remove', this.onStoreRemove, this);
6728         this.stopEditing(false);
6729         Ext.destroy(this.btns);
6730     },
6731
6732     refreshFields: function(){
6733         this.initFields();
6734         this.verifyLayout();
6735     },
6736
6737     isDirty: function(){
6738         var dirty;
6739         this.items.each(function(f){
6740             if(String(this.values[f.id]) !== String(f.getValue())){
6741                 dirty = true;
6742                 return false;
6743             }
6744         }, this);
6745         return dirty;
6746     },
6747
6748     startEditing: function(rowIndex, doFocus){
6749         if(this.editing && this.isDirty()){
6750             this.showTooltip(this.commitChangesText);
6751             return;
6752         }
6753         if(Ext.isObject(rowIndex)){
6754             rowIndex = this.grid.getStore().indexOf(rowIndex);
6755         }
6756         if(this.fireEvent('beforeedit', this, rowIndex) !== false){
6757             this.editing = true;
6758             var g = this.grid, view = g.getView(),
6759                 row = view.getRow(rowIndex),
6760                 record = g.store.getAt(rowIndex);
6761                 
6762             this.record = record;
6763             this.rowIndex = rowIndex;
6764             this.values = {};
6765             if(!this.rendered){
6766                 this.render(view.getEditorParent());
6767             }
6768             var w = Ext.fly(row).getWidth();
6769             this.setSize(w);
6770             if(!this.initialized){
6771                 this.initFields();
6772             }
6773             var cm = g.getColumnModel(), fields = this.items.items, f, val;
6774             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6775                 val = this.preEditValue(record, cm.getDataIndex(i));
6776                 f = fields[i];
6777                 f.setValue(val);
6778                 this.values[f.id] = Ext.isEmpty(val) ? '' : val;
6779             }
6780             this.verifyLayout(true);
6781             if(!this.isVisible()){
6782                 this.setPagePosition(Ext.fly(row).getXY());
6783             } else{
6784                 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
6785             }
6786             if(!this.isVisible()){
6787                 this.show().doLayout();
6788             }
6789             if(doFocus !== false){
6790                 this.doFocus.defer(this.focusDelay, this);
6791             }
6792         }
6793     },
6794
6795     stopEditing : function(saveChanges){
6796         this.editing = false;
6797         if(!this.isVisible()){
6798             return;
6799         }
6800         if(saveChanges === false || !this.isValid()){
6801             this.hide();
6802             this.fireEvent('canceledit', this, saveChanges === false);
6803             return;
6804         }
6805         var changes = {}, 
6806             r = this.record, 
6807             hasChange = false,
6808             cm = this.grid.colModel, 
6809             fields = this.items.items;
6810         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6811             if(!cm.isHidden(i)){
6812                 var dindex = cm.getDataIndex(i);
6813                 if(!Ext.isEmpty(dindex)){
6814                     var oldValue = r.data[dindex],
6815                         value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
6816                     if(String(oldValue) !== String(value)){
6817                         changes[dindex] = value;
6818                         hasChange = true;
6819                     }
6820                 }
6821             }
6822         }
6823         if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
6824             r.beginEdit();
6825             Ext.iterate(changes, function(name, value){
6826                 r.set(name, value);
6827             });
6828             r.endEdit();
6829             this.fireEvent('afteredit', this, changes, r, this.rowIndex);
6830         }
6831         this.hide();
6832     },
6833
6834     verifyLayout: function(force){
6835         if(this.el && (this.isVisible() || force === true)){
6836             var row = this.grid.getView().getRow(this.rowIndex);
6837             this.setSize(Ext.fly(row).getWidth(), Ext.fly(row).getHeight() + 9);
6838             var cm = this.grid.colModel, fields = this.items.items;
6839             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6840                 if(!cm.isHidden(i)){
6841                     var adjust = 0;
6842                     if(i === (len - 1)){
6843                         adjust += 3; // outer padding
6844                     } else{
6845                         adjust += 1;
6846                     }
6847                     fields[i].show();
6848                     fields[i].setWidth(cm.getColumnWidth(i) - adjust);
6849                 } else{
6850                     fields[i].hide();
6851                 }
6852             }
6853             this.doLayout();
6854             this.positionButtons();
6855         }
6856     },
6857
6858     slideHide : function(){
6859         this.hide();
6860     },
6861
6862     initFields: function(){
6863         var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
6864         this.removeAll(false);
6865         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6866             var c = cm.getColumnAt(i),
6867                 ed = c.getEditor();
6868             if(!ed){
6869                 ed = c.displayEditor || new Ext.form.DisplayField();
6870             }
6871             if(i == 0){
6872                 ed.margins = pm('0 1 2 1');
6873             } else if(i == len - 1){
6874                 ed.margins = pm('0 0 2 1');
6875             } else{
6876                 ed.margins = pm('0 1 2');
6877             }
6878             ed.setWidth(cm.getColumnWidth(i));
6879             ed.column = c;
6880             if(ed.ownerCt !== this){
6881                 ed.on('focus', this.ensureVisible, this);
6882                 ed.on('specialkey', this.onKey, this);
6883             }
6884             this.insert(i, ed);
6885         }
6886         this.initialized = true;
6887     },
6888
6889     onKey: function(f, e){
6890         if(e.getKey() === e.ENTER){
6891             this.stopEditing(true);
6892             e.stopPropagation();
6893         }
6894     },
6895
6896     onGridKey: function(e){
6897         if(e.getKey() === e.ENTER && !this.isVisible()){
6898             var r = this.grid.getSelectionModel().getSelected();
6899             if(r){
6900                 var index = this.grid.store.indexOf(r);
6901                 this.startEditing(index);
6902                 e.stopPropagation();
6903             }
6904         }
6905     },
6906
6907     ensureVisible: function(editor){
6908         if(this.isVisible()){
6909              this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
6910         }
6911     },
6912
6913     onRowClick: function(g, rowIndex, e){
6914         if(this.clicksToEdit == 'auto'){
6915             var li = this.lastClickIndex;
6916             this.lastClickIndex = rowIndex;
6917             if(li != rowIndex && !this.isVisible()){
6918                 return;
6919             }
6920         }
6921         this.startEditing(rowIndex, false);
6922         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
6923     },
6924
6925     onRowDblClick: function(g, rowIndex, e){
6926         this.startEditing(rowIndex, false);
6927         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
6928     },
6929
6930     onRender: function(){
6931         Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
6932         this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
6933         this.btns = new Ext.Panel({
6934             baseCls: 'x-plain',
6935             cls: 'x-btns',
6936             elements:'body',
6937             layout: 'table',
6938             width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
6939             items: [{
6940                 ref: 'saveBtn',
6941                 itemId: 'saveBtn',
6942                 xtype: 'button',
6943                 text: this.saveText,
6944                 width: this.minButtonWidth,
6945                 handler: this.stopEditing.createDelegate(this, [true])
6946             }, {
6947                 xtype: 'button',
6948                 text: this.cancelText,
6949                 width: this.minButtonWidth,
6950                 handler: this.stopEditing.createDelegate(this, [false])
6951             }]
6952         });
6953         this.btns.render(this.bwrap);
6954     },
6955
6956     afterRender: function(){
6957         Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
6958         this.positionButtons();
6959         if(this.monitorValid){
6960             this.startMonitoring();
6961         }
6962     },
6963
6964     onShow: function(){
6965         if(this.monitorValid){
6966             this.startMonitoring();
6967         }
6968         Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
6969     },
6970
6971     onHide: function(){
6972         Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
6973         this.stopMonitoring();
6974         this.grid.getView().focusRow(this.rowIndex);
6975     },
6976
6977     positionButtons: function(){
6978         if(this.btns){
6979             var g = this.grid,
6980                 h = this.el.dom.clientHeight,
6981                 view = g.getView(),
6982                 scroll = view.scroller.dom.scrollLeft,
6983                 bw = this.btns.getWidth(),
6984                 width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());
6985                 
6986             this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
6987         }
6988     },
6989
6990     // private
6991     preEditValue : function(r, field){
6992         var value = r.data[field];
6993         return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
6994     },
6995
6996     // private
6997     postEditValue : function(value, originalValue, r, field){
6998         return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
6999     },
7000
7001     doFocus: function(pt){
7002         if(this.isVisible()){
7003             var index = 0,
7004                 cm = this.grid.getColumnModel(),
7005                 c;
7006             if(pt){
7007                 index = this.getTargetColumnIndex(pt);
7008             }
7009             for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
7010                 c = cm.getColumnAt(i);
7011                 if(!c.hidden && c.getEditor()){
7012                     c.getEditor().focus();
7013                     break;
7014                 }
7015             }
7016         }
7017     },
7018
7019     getTargetColumnIndex: function(pt){
7020         var grid = this.grid, 
7021             v = grid.view,
7022             x = pt.left,
7023             cms = grid.colModel.config,
7024             i = 0, 
7025             match = false;
7026         for(var len = cms.length, c; c = cms[i]; i++){
7027             if(!c.hidden){
7028                 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
7029                     match = i;
7030                     break;
7031                 }
7032             }
7033         }
7034         return match;
7035     },
7036
7037     startMonitoring : function(){
7038         if(!this.bound && this.monitorValid){
7039             this.bound = true;
7040             Ext.TaskMgr.start({
7041                 run : this.bindHandler,
7042                 interval : this.monitorPoll || 200,
7043                 scope: this
7044             });
7045         }
7046     },
7047
7048     stopMonitoring : function(){
7049         this.bound = false;
7050         if(this.tooltip){
7051             this.tooltip.hide();
7052         }
7053     },
7054
7055     isValid: function(){
7056         var valid = true;
7057         this.items.each(function(f){
7058             if(!f.isValid(true)){
7059                 valid = false;
7060                 return false;
7061             }
7062         });
7063         return valid;
7064     },
7065
7066     // private
7067     bindHandler : function(){
7068         if(!this.bound){
7069             return false; // stops binding
7070         }
7071         var valid = this.isValid();
7072         if(!valid && this.errorSummary){
7073             this.showTooltip(this.getErrorText().join(''));
7074         }
7075         this.btns.saveBtn.setDisabled(!valid);
7076         this.fireEvent('validation', this, valid);
7077     },
7078
7079     showTooltip: function(msg){
7080         var t = this.tooltip;
7081         if(!t){
7082             t = this.tooltip = new Ext.ToolTip({
7083                 maxWidth: 600,
7084                 cls: 'errorTip',
7085                 width: 300,
7086                 title: this.errorText,
7087                 autoHide: false,
7088                 anchor: 'left',
7089                 anchorToTarget: true,
7090                 mouseOffset: [40,0]
7091             });
7092         }
7093         var v = this.grid.getView(),
7094             top = parseInt(this.el.dom.style.top, 10),
7095             scroll = v.scroller.dom.scrollTop,
7096             h = this.el.getHeight();
7097                 
7098         if(top + h >= scroll){
7099             t.initTarget(this.items.last().getEl());
7100             if(!t.rendered){
7101                 t.show();
7102                 t.hide();
7103             }
7104             t.body.update(msg);
7105             t.doAutoWidth(20);
7106             t.show();
7107         }else if(t.rendered){
7108             t.hide();
7109         }
7110     },
7111
7112     getErrorText: function(){
7113         var data = ['<ul>'];
7114         this.items.each(function(f){
7115             if(!f.isValid(true)){
7116                 data.push('<li>', f.getActiveError(), '</li>');
7117             }
7118         });
7119         data.push('</ul>');
7120         return data;
7121     }
7122 });
7123 Ext.preg('roweditor', Ext.ux.grid.RowEditor);
7124 Ext.ns('Ext.ux.grid');\r
7125 \r
7126 /**\r
7127  * @class Ext.ux.grid.RowExpander\r
7128  * @extends Ext.util.Observable\r
7129  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables\r
7130  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react\r
7131  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.\r
7132  *\r
7133  * @ptype rowexpander\r
7134  */\r
7135 Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {\r
7136     /**\r
7137      * @cfg {Boolean} expandOnEnter\r
7138      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter\r
7139      * key is pressed (defaults to <tt>true</tt>).\r
7140      */\r
7141     expandOnEnter : true,\r
7142     /**\r
7143      * @cfg {Boolean} expandOnDblClick\r
7144      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked\r
7145      * (defaults to <tt>true</tt>).\r
7146      */\r
7147     expandOnDblClick : true,\r
7148 \r
7149     header : '',\r
7150     width : 20,\r
7151     sortable : false,\r
7152     fixed : true,\r
7153     menuDisabled : true,\r
7154     dataIndex : '',\r
7155     id : 'expander',\r
7156     lazyRender : true,\r
7157     enableCaching : true,\r
7158 \r
7159     constructor: function(config){\r
7160         Ext.apply(this, config);\r
7161 \r
7162         this.addEvents({\r
7163             /**\r
7164              * @event beforeexpand\r
7165              * Fires before the row expands. Have the listener return false to prevent the row from expanding.\r
7166              * @param {Object} this RowExpander object.\r
7167              * @param {Object} Ext.data.Record Record for the selected row.\r
7168              * @param {Object} body body element for the secondary row.\r
7169              * @param {Number} rowIndex The current row index.\r
7170              */\r
7171             beforeexpand: true,\r
7172             /**\r
7173              * @event expand\r
7174              * Fires after the row expands.\r
7175              * @param {Object} this RowExpander object.\r
7176              * @param {Object} Ext.data.Record Record for the selected row.\r
7177              * @param {Object} body body element for the secondary row.\r
7178              * @param {Number} rowIndex The current row index.\r
7179              */\r
7180             expand: true,\r
7181             /**\r
7182              * @event beforecollapse\r
7183              * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.\r
7184              * @param {Object} this RowExpander object.\r
7185              * @param {Object} Ext.data.Record Record for the selected row.\r
7186              * @param {Object} body body element for the secondary row.\r
7187              * @param {Number} rowIndex The current row index.\r
7188              */\r
7189             beforecollapse: true,\r
7190             /**\r
7191              * @event collapse\r
7192              * Fires after the row collapses.\r
7193              * @param {Object} this RowExpander object.\r
7194              * @param {Object} Ext.data.Record Record for the selected row.\r
7195              * @param {Object} body body element for the secondary row.\r
7196              * @param {Number} rowIndex The current row index.\r
7197              */\r
7198             collapse: true\r
7199         });\r
7200 \r
7201         Ext.ux.grid.RowExpander.superclass.constructor.call(this);\r
7202 \r
7203         if(this.tpl){\r
7204             if(typeof this.tpl == 'string'){\r
7205                 this.tpl = new Ext.Template(this.tpl);\r
7206             }\r
7207             this.tpl.compile();\r
7208         }\r
7209 \r
7210         this.state = {};\r
7211         this.bodyContent = {};\r
7212     },\r
7213 \r
7214     getRowClass : function(record, rowIndex, p, ds){\r
7215         p.cols = p.cols-1;\r
7216         var content = this.bodyContent[record.id];\r
7217         if(!content && !this.lazyRender){\r
7218             content = this.getBodyContent(record, rowIndex);\r
7219         }\r
7220         if(content){\r
7221             p.body = content;\r
7222         }\r
7223         return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';\r
7224     },\r
7225 \r
7226     init : function(grid){\r
7227         this.grid = grid;\r
7228 \r
7229         var view = grid.getView();\r
7230         view.getRowClass = this.getRowClass.createDelegate(this);\r
7231 \r
7232         view.enableRowBody = true;\r
7233 \r
7234 \r
7235         grid.on('render', this.onRender, this);\r
7236         grid.on('destroy', this.onDestroy, this);\r
7237     },\r
7238 \r
7239     // @private\r
7240     onRender: function() {\r
7241         var grid = this.grid;\r
7242         var mainBody = grid.getView().mainBody;\r
7243         mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});\r
7244         if (this.expandOnEnter) {\r
7245             this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {\r
7246                 'enter' : this.onEnter,\r
7247                 scope: this\r
7248             });\r
7249         }\r
7250         if (this.expandOnDblClick) {\r
7251             grid.on('rowdblclick', this.onRowDblClick, this);\r
7252         }\r
7253     },\r
7254     \r
7255     // @private    \r
7256     onDestroy: function() {\r
7257         if(this.keyNav){\r
7258             this.keyNav.disable();\r
7259             delete this.keyNav;\r
7260         }\r
7261         /*\r
7262          * A majority of the time, the plugin will be destroyed along with the grid,\r
7263          * which means the mainBody won't be available. On the off chance that the plugin\r
7264          * isn't destroyed with the grid, take care of removing the listener.\r
7265          */\r
7266         var mainBody = this.grid.getView().mainBody;\r
7267         if(mainBody){\r
7268             mainBody.un('mousedown', this.onMouseDown, this);\r
7269         }\r
7270     },\r
7271     // @private\r
7272     onRowDblClick: function(grid, rowIdx, e) {\r
7273         this.toggleRow(rowIdx);\r
7274     },\r
7275 \r
7276     onEnter: function(e) {\r
7277         var g = this.grid;\r
7278         var sm = g.getSelectionModel();\r
7279         var sels = sm.getSelections();\r
7280         for (var i = 0, len = sels.length; i < len; i++) {\r
7281             var rowIdx = g.getStore().indexOf(sels[i]);\r
7282             this.toggleRow(rowIdx);\r
7283         }\r
7284     },\r
7285 \r
7286     getBodyContent : function(record, index){\r
7287         if(!this.enableCaching){\r
7288             return this.tpl.apply(record.data);\r
7289         }\r
7290         var content = this.bodyContent[record.id];\r
7291         if(!content){\r
7292             content = this.tpl.apply(record.data);\r
7293             this.bodyContent[record.id] = content;\r
7294         }\r
7295         return content;\r
7296     },\r
7297 \r
7298     onMouseDown : function(e, t){\r
7299         e.stopEvent();\r
7300         var row = e.getTarget('.x-grid3-row');\r
7301         this.toggleRow(row);\r
7302     },\r
7303 \r
7304     renderer : function(v, p, record){\r
7305         p.cellAttr = 'rowspan="2"';\r
7306         return '<div class="x-grid3-row-expander">&#160;</div>';\r
7307     },\r
7308 \r
7309     beforeExpand : function(record, body, rowIndex){\r
7310         if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){\r
7311             if(this.tpl && this.lazyRender){\r
7312                 body.innerHTML = this.getBodyContent(record, rowIndex);\r
7313             }\r
7314             return true;\r
7315         }else{\r
7316             return false;\r
7317         }\r
7318     },\r
7319 \r
7320     toggleRow : function(row){\r
7321         if(typeof row == 'number'){\r
7322             row = this.grid.view.getRow(row);\r
7323         }\r
7324         this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);\r
7325     },\r
7326 \r
7327     expandRow : function(row){\r
7328         if(typeof row == 'number'){\r
7329             row = this.grid.view.getRow(row);\r
7330         }\r
7331         var record = this.grid.store.getAt(row.rowIndex);\r
7332         var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);\r
7333         if(this.beforeExpand(record, body, row.rowIndex)){\r
7334             this.state[record.id] = true;\r
7335             Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');\r
7336             this.fireEvent('expand', this, record, body, row.rowIndex);\r
7337         }\r
7338     },\r
7339 \r
7340     collapseRow : function(row){\r
7341         if(typeof row == 'number'){\r
7342             row = this.grid.view.getRow(row);\r
7343         }\r
7344         var record = this.grid.store.getAt(row.rowIndex);\r
7345         var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);\r
7346         if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){\r
7347             this.state[record.id] = false;\r
7348             Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');\r
7349             this.fireEvent('collapse', this, record, body, row.rowIndex);\r
7350         }\r
7351     }\r
7352 });\r
7353 \r
7354 Ext.preg('rowexpander', Ext.ux.grid.RowExpander);\r
7355 \r
7356 //backwards compat\r
7357 Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not
7358 // exist by default in Ext, so we have to add the namespace first:
7359 Ext.ns('Ext.ux.layout');
7360
7361 /**
7362  * @class Ext.ux.layout.RowLayout
7363  * @extends Ext.layout.ContainerLayout
7364  * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
7365  * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
7366  * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
7367  * and should generally not need to be created directly via the new keyword.</p>
7368  * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
7369  * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
7370  * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
7371  * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
7372  * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
7373  * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and
7374  * less than 1 (e.g., .25).</p>
7375  * <p>The basic rules for specifying row heights are pretty simple.  The logic makes two passes through the
7376  * set of contained panels.  During the first layout pass, all panels that either have a fixed height or none
7377  * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
7378  * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
7379  * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
7380  * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
7381  * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your
7382  * layout may not render as expected.  Example usage:</p>
7383  * <pre><code>
7384 // All rows are percentages -- they must add up to 1
7385 var p = new Ext.Panel({
7386     title: 'Row Layout - Percentage Only',
7387     layout:'ux.row',
7388     items: [{
7389         title: 'Row 1',
7390         rowHeight: .25
7391     },{
7392         title: 'Row 2',
7393         rowHeight: .6
7394     },{
7395         title: 'Row 3',
7396         rowHeight: .15
7397     }]
7398 });
7399
7400 // Mix of height and rowHeight -- all rowHeight values must add
7401 // up to 1. The first row will take up exactly 120px, and the last two
7402 // rows will fill the remaining container height.
7403 var p = new Ext.Panel({
7404     title: 'Row Layout - Mixed',
7405     layout:'ux.row',
7406     items: [{
7407         title: 'Row 1',
7408         height: 120,
7409         // standard panel widths are still supported too:
7410         width: '50%' // or 200
7411     },{
7412         title: 'Row 2',
7413         rowHeight: .8,
7414         width: 300
7415     },{
7416         title: 'Row 3',
7417         rowHeight: .2
7418     }]
7419 });
7420 </code></pre>
7421  */
7422 Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
7423     // private
7424     monitorResize:true,
7425
7426     // private
7427     isValidParent : function(c, target){
7428         return c.getEl().dom.parentNode == this.innerCt.dom;
7429     },
7430
7431     // private
7432     onLayout : function(ct, target){
7433         var rs = ct.items.items, len = rs.length, r, i;
7434
7435         if(!this.innerCt){
7436             target.addClass('ux-row-layout-ct');
7437             this.innerCt = target.createChild({cls:'x-row-inner'});
7438         }
7439         this.renderAll(ct, this.innerCt);
7440
7441         var size = target.getViewSize(true);
7442
7443         if(size.width < 1 && size.height < 1){ // display none?
7444             return;
7445         }
7446
7447         var h = size.height,
7448             ph = h;
7449
7450         this.innerCt.setSize({height:h});
7451
7452         // some rows can be percentages while others are fixed
7453         // so we need to make 2 passes
7454
7455         for(i = 0; i < len; i++){
7456             r = rs[i];
7457             if(!r.rowHeight){
7458                 ph -= (r.getSize().height + r.getEl().getMargins('tb'));
7459             }
7460         }
7461
7462         ph = ph < 0 ? 0 : ph;
7463
7464         for(i = 0; i < len; i++){
7465             r = rs[i];
7466             if(r.rowHeight){
7467                 r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
7468             }
7469         }
7470     }
7471
7472     /**
7473      * @property activeItem
7474      * @hide
7475      */
7476 });
7477
7478 Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
7479 Ext.ns('Ext.ux.form');\r
7480 \r
7481 Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {\r
7482     initComponent : function(){\r
7483         Ext.ux.form.SearchField.superclass.initComponent.call(this);\r
7484         this.on('specialkey', function(f, e){\r
7485             if(e.getKey() == e.ENTER){\r
7486                 this.onTrigger2Click();\r
7487             }\r
7488         }, this);\r
7489     },\r
7490 \r
7491     validationEvent:false,\r
7492     validateOnBlur:false,\r
7493     trigger1Class:'x-form-clear-trigger',\r
7494     trigger2Class:'x-form-search-trigger',\r
7495     hideTrigger1:true,\r
7496     width:180,\r
7497     hasSearch : false,\r
7498     paramName : 'query',\r
7499 \r
7500     onTrigger1Click : function(){\r
7501         if(this.hasSearch){\r
7502             this.el.dom.value = '';\r
7503             var o = {start: 0};\r
7504             this.store.baseParams = this.store.baseParams || {};\r
7505             this.store.baseParams[this.paramName] = '';\r
7506             this.store.reload({params:o});\r
7507             this.triggers[0].hide();\r
7508             this.hasSearch = false;\r
7509         }\r
7510     },\r
7511 \r
7512     onTrigger2Click : function(){\r
7513         var v = this.getRawValue();\r
7514         if(v.length < 1){\r
7515             this.onTrigger1Click();\r
7516             return;\r
7517         }\r
7518         var o = {start: 0};\r
7519         this.store.baseParams = this.store.baseParams || {};\r
7520         this.store.baseParams[this.paramName] = v;\r
7521         this.store.reload({params:o});\r
7522         this.hasSearch = true;\r
7523         this.triggers[0].show();\r
7524     }\r
7525 });Ext.ns('Ext.ux.form');\r
7526 \r
7527 /**\r
7528  * @class Ext.ux.form.SelectBox\r
7529  * @extends Ext.form.ComboBox\r
7530  * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging\r
7531  * through the list, with item selection occurring when the mouse button is released.\r
7532  * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}\r
7533  * on inner elements.  Re-enabling editable after calling this will NOT work.</p>\r
7534  * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392\r
7535  * @history 2007-07-08 jvs\r
7536  * Slight mods for Ext 2.0\r
7537  * @xtype selectbox\r
7538  */\r
7539 Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {\r
7540         constructor: function(config){\r
7541                 this.searchResetDelay = 1000;\r
7542                 config = config || {};\r
7543                 config = Ext.apply(config || {}, {\r
7544                         editable: false,\r
7545                         forceSelection: true,\r
7546                         rowHeight: false,\r
7547                         lastSearchTerm: false,\r
7548                         triggerAction: 'all',\r
7549                         mode: 'local'\r
7550                 });\r
7551 \r
7552                 Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);\r
7553 \r
7554                 this.lastSelectedIndex = this.selectedIndex || 0;\r
7555         },\r
7556 \r
7557         initEvents : function(){\r
7558                 Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);\r
7559                 // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE\r
7560                 this.el.on('keydown', this.keySearch, this, true);\r
7561                 this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);\r
7562         },\r
7563 \r
7564         keySearch : function(e, target, options) {\r
7565                 var raw = e.getKey();\r
7566                 var key = String.fromCharCode(raw);\r
7567                 var startIndex = 0;\r
7568 \r
7569                 if( !this.store.getCount() ) {\r
7570                         return;\r
7571                 }\r
7572 \r
7573                 switch(raw) {\r
7574                         case Ext.EventObject.HOME:\r
7575                                 e.stopEvent();\r
7576                                 this.selectFirst();\r
7577                                 return;\r
7578 \r
7579                         case Ext.EventObject.END:\r
7580                                 e.stopEvent();\r
7581                                 this.selectLast();\r
7582                                 return;\r
7583 \r
7584                         case Ext.EventObject.PAGEDOWN:\r
7585                                 this.selectNextPage();\r
7586                                 e.stopEvent();\r
7587                                 return;\r
7588 \r
7589                         case Ext.EventObject.PAGEUP:\r
7590                                 this.selectPrevPage();\r
7591                                 e.stopEvent();\r
7592                                 return;\r
7593                 }\r
7594 \r
7595                 // skip special keys other than the shift key\r
7596                 if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {\r
7597                         return;\r
7598                 }\r
7599                 if( this.lastSearchTerm == key ) {\r
7600                         startIndex = this.lastSelectedIndex;\r
7601                 }\r
7602                 this.search(this.displayField, key, startIndex);\r
7603                 this.cshTask.delay(this.searchResetDelay);\r
7604         },\r
7605 \r
7606         onRender : function(ct, position) {\r
7607                 this.store.on('load', this.calcRowsPerPage, this);\r
7608                 Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);\r
7609                 if( this.mode == 'local' ) {\r
7610             this.initList();\r
7611                         this.calcRowsPerPage();\r
7612                 }\r
7613         },\r
7614 \r
7615         onSelect : function(record, index, skipCollapse){\r
7616                 if(this.fireEvent('beforeselect', this, record, index) !== false){\r
7617                         this.setValue(record.data[this.valueField || this.displayField]);\r
7618                         if( !skipCollapse ) {\r
7619                                 this.collapse();\r
7620                         }\r
7621                         this.lastSelectedIndex = index + 1;\r
7622                         this.fireEvent('select', this, record, index);\r
7623                 }\r
7624         },\r
7625 \r
7626         afterRender : function() {\r
7627                 Ext.ux.form.SelectBox.superclass.afterRender.apply(this, arguments);\r
7628                 if(Ext.isWebKit) {\r
7629                         this.el.swallowEvent('mousedown', true);\r
7630                 }\r
7631                 this.el.unselectable();\r
7632                 this.innerList.unselectable();\r
7633                 this.trigger.unselectable();\r
7634                 this.innerList.on('mouseup', function(e, target, options) {\r
7635                         if( target.id && target.id == this.innerList.id ) {\r
7636                                 return;\r
7637                         }\r
7638                         this.onViewClick();\r
7639                 }, this);\r
7640 \r
7641                 this.innerList.on('mouseover', function(e, target, options) {\r
7642                         if( target.id && target.id == this.innerList.id ) {\r
7643                                 return;\r
7644                         }\r
7645                         this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;\r
7646                         this.cshTask.delay(this.searchResetDelay);\r
7647                 }, this);\r
7648 \r
7649                 this.trigger.un('click', this.onTriggerClick, this);\r
7650                 this.trigger.on('mousedown', function(e, target, options) {\r
7651                         e.preventDefault();\r
7652                         this.onTriggerClick();\r
7653                 }, this);\r
7654 \r
7655                 this.on('collapse', function(e, target, options) {\r
7656                         Ext.getDoc().un('mouseup', this.collapseIf, this);\r
7657                 }, this, true);\r
7658 \r
7659                 this.on('expand', function(e, target, options) {\r
7660                         Ext.getDoc().on('mouseup', this.collapseIf, this);\r
7661                 }, this, true);\r
7662         },\r
7663 \r
7664         clearSearchHistory : function() {\r
7665                 this.lastSelectedIndex = 0;\r
7666                 this.lastSearchTerm = false;\r
7667         },\r
7668 \r
7669         selectFirst : function() {\r
7670                 this.focusAndSelect(this.store.data.first());\r
7671         },\r
7672 \r
7673         selectLast : function() {\r
7674                 this.focusAndSelect(this.store.data.last());\r
7675         },\r
7676 \r
7677         selectPrevPage : function() {\r
7678                 if( !this.rowHeight ) {\r
7679                         return;\r
7680                 }\r
7681                 var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);\r
7682                 this.focusAndSelect(this.store.getAt(index));\r
7683         },\r
7684 \r
7685         selectNextPage : function() {\r
7686                 if( !this.rowHeight ) {\r
7687                         return;\r
7688                 }\r
7689                 var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);\r
7690                 this.focusAndSelect(this.store.getAt(index));\r
7691         },\r
7692 \r
7693         search : function(field, value, startIndex) {\r
7694                 field = field || this.displayField;\r
7695                 this.lastSearchTerm = value;\r
7696                 var index = this.store.find.apply(this.store, arguments);\r
7697                 if( index !== -1 ) {\r
7698                         this.focusAndSelect(index);\r
7699                 }\r
7700         },\r
7701 \r
7702         focusAndSelect : function(record) {\r
7703         var index = Ext.isNumber(record) ? record : this.store.indexOf(record);\r
7704         this.select(index, this.isExpanded());\r
7705         this.onSelect(this.store.getAt(index), index, this.isExpanded());\r
7706         },\r
7707 \r
7708         calcRowsPerPage : function() {\r
7709                 if( this.store.getCount() ) {\r
7710                         this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();\r
7711                         this.rowsPerPage = this.maxHeight / this.rowHeight;\r
7712                 } else {\r
7713                         this.rowHeight = false;\r
7714                 }\r
7715         }\r
7716 \r
7717 });\r
7718 \r
7719 Ext.reg('selectbox', Ext.ux.form.SelectBox);\r
7720 \r
7721 //backwards compat\r
7722 Ext.ux.SelectBox = Ext.ux.form.SelectBox;\r
7723 /**\r
7724  * @class Ext.ux.SliderTip\r
7725  * @extends Ext.Tip\r
7726  * Simple plugin for using an Ext.Tip with a slider to show the slider value\r
7727  */\r
7728 Ext.ux.SliderTip = Ext.extend(Ext.Tip, {\r
7729     minWidth: 10,\r
7730     offsets : [0, -10],\r
7731     init : function(slider){\r
7732         slider.on('dragstart', this.onSlide, this);\r
7733         slider.on('drag', this.onSlide, this);\r
7734         slider.on('dragend', this.hide, this);\r
7735         slider.on('destroy', this.destroy, this);\r
7736     },\r
7737 \r
7738     onSlide : function(slider){\r
7739         this.show();\r
7740         this.body.update(this.getText(slider));\r
7741         this.doAutoWidth();\r
7742         this.el.alignTo(slider.thumb, 'b-t?', this.offsets);\r
7743     },\r
7744 \r
7745     getText : function(slider){\r
7746         return String(slider.getValue());\r
7747     }\r
7748 });\r
7749 Ext.ux.SlidingPager = Ext.extend(Object, {\r
7750     init : function(pbar){\r
7751         Ext.each(pbar.items.getRange(2,6), function(c){\r
7752             c.hide();\r
7753         });\r
7754         var slider = new Ext.Slider({\r
7755             width: 114,\r
7756             minValue: 1,\r
7757             maxValue: 1,\r
7758             plugins: new Ext.ux.SliderTip({\r
7759                 getText : function(s){\r
7760                     return String.format('Page <b>{0}</b> of <b>{1}</b>', s.value, s.maxValue);\r
7761                 }\r
7762             }),\r
7763             listeners: {\r
7764                 changecomplete: function(s, v){\r
7765                     pbar.changePage(v);\r
7766                 }\r
7767             }\r
7768         });\r
7769         pbar.insert(5, slider);\r
7770         pbar.on({\r
7771             change: function(pb, data){\r
7772                 slider.maxValue = data.pages;\r
7773                 slider.setValue(data.activePage);\r
7774             },\r
7775             beforedestroy: function(){\r
7776                 slider.destroy();\r
7777             }\r
7778         });\r
7779     }\r
7780 });Ext.ns('Ext.ux.form');\r
7781 \r
7782 /**\r
7783  * @class Ext.ux.form.SpinnerField\r
7784  * @extends Ext.form.NumberField\r
7785  * Creates a field utilizing Ext.ux.Spinner\r
7786  * @xtype spinnerfield\r
7787  */\r
7788 Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {\r
7789     actionMode: 'wrap',\r
7790     deferHeight: true,\r
7791     autoSize: Ext.emptyFn,\r
7792     onBlur: Ext.emptyFn,\r
7793     adjustSize: Ext.BoxComponent.prototype.adjustSize,\r
7794 \r
7795         constructor: function(config) {\r
7796                 var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');\r
7797 \r
7798                 var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);\r
7799 \r
7800                 var plugins = config.plugins\r
7801                         ? (Ext.isArray(config.plugins)\r
7802                                 ? config.plugins.push(spl)\r
7803                                 : [config.plugins, spl])\r
7804                         : spl;\r
7805 \r
7806                 Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));\r
7807         },\r
7808 \r
7809     // private\r
7810     getResizeEl: function(){\r
7811         return this.wrap;\r
7812     },\r
7813 \r
7814     // private\r
7815     getPositionEl: function(){\r
7816         return this.wrap;\r
7817     },\r
7818 \r
7819     // private\r
7820     alignErrorIcon: function(){\r
7821         if (this.wrap) {\r
7822             this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);\r
7823         }\r
7824     },\r
7825 \r
7826     validateBlur: function(){\r
7827         return true;\r
7828     }\r
7829 });\r
7830 \r
7831 Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);\r
7832 \r
7833 //backwards compat\r
7834 Ext.form.SpinnerField = Ext.ux.form.SpinnerField;\r
7835 /**\r
7836  * @class Ext.ux.Spinner\r
7837  * @extends Ext.util.Observable\r
7838  * Creates a Spinner control utilized by Ext.ux.form.SpinnerField\r
7839  */\r
7840 Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {\r
7841     incrementValue: 1,\r
7842     alternateIncrementValue: 5,\r
7843     triggerClass: 'x-form-spinner-trigger',\r
7844     splitterClass: 'x-form-spinner-splitter',\r
7845     alternateKey: Ext.EventObject.shiftKey,\r
7846     defaultValue: 0,\r
7847     accelerate: false,\r
7848 \r
7849     constructor: function(config){\r
7850         Ext.ux.Spinner.superclass.constructor.call(this, config);\r
7851         Ext.apply(this, config);\r
7852         this.mimicing = false;\r
7853     },\r
7854 \r
7855     init: function(field){\r
7856         this.field = field;\r
7857 \r
7858         field.afterMethod('onRender', this.doRender, this);\r
7859         field.afterMethod('onEnable', this.doEnable, this);\r
7860         field.afterMethod('onDisable', this.doDisable, this);\r
7861         field.afterMethod('afterRender', this.doAfterRender, this);\r
7862         field.afterMethod('onResize', this.doResize, this);\r
7863         field.afterMethod('onFocus', this.doFocus, this);\r
7864         field.beforeMethod('onDestroy', this.doDestroy, this);\r
7865     },\r
7866 \r
7867     doRender: function(ct, position){\r
7868         var el = this.el = this.field.getEl();\r
7869         var f = this.field;\r
7870 \r
7871         if (!f.wrap) {\r
7872             f.wrap = this.wrap = el.wrap({\r
7873                 cls: "x-form-field-wrap"\r
7874             });\r
7875         }\r
7876         else {\r
7877             this.wrap = f.wrap.addClass('x-form-field-wrap');\r
7878         }\r
7879 \r
7880         this.trigger = this.wrap.createChild({\r
7881             tag: "img",\r
7882             src: Ext.BLANK_IMAGE_URL,\r
7883             cls: "x-form-trigger " + this.triggerClass\r
7884         });\r
7885 \r
7886         if (!f.width) {\r
7887             this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());\r
7888         }\r
7889 \r
7890         this.splitter = this.wrap.createChild({\r
7891             tag: 'div',\r
7892             cls: this.splitterClass,\r
7893             style: 'width:13px; height:2px;'\r
7894         });\r
7895         this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();\r
7896 \r
7897         this.proxy = this.trigger.createProxy('', this.splitter, true);\r
7898         this.proxy.addClass("x-form-spinner-proxy");\r
7899         this.proxy.setStyle('left', '0px');\r
7900         this.proxy.setSize(14, 1);\r
7901         this.proxy.hide();\r
7902         this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {\r
7903             dragElId: this.proxy.id\r
7904         });\r
7905 \r
7906         this.initTrigger();\r
7907         this.initSpinner();\r
7908     },\r
7909 \r
7910     doAfterRender: function(){\r
7911         var y;\r
7912         if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {\r
7913             this.el.position();\r
7914             this.el.setY(y);\r
7915         }\r
7916     },\r
7917 \r
7918     doEnable: function(){\r
7919         if (this.wrap) {\r
7920             this.wrap.removeClass(this.field.disabledClass);\r
7921         }\r
7922     },\r
7923 \r
7924     doDisable: function(){\r
7925         if (this.wrap) {\r
7926             this.wrap.addClass(this.field.disabledClass);\r
7927             this.el.removeClass(this.field.disabledClass);\r
7928         }\r
7929     },\r
7930 \r
7931     doResize: function(w, h){\r
7932         if (typeof w == 'number') {\r
7933             this.el.setWidth(w - this.trigger.getWidth());\r
7934         }\r
7935         this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());\r
7936     },\r
7937 \r
7938     doFocus: function(){\r
7939         if (!this.mimicing) {\r
7940             this.wrap.addClass('x-trigger-wrap-focus');\r
7941             this.mimicing = true;\r
7942             Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {\r
7943                 delay: 10\r
7944             });\r
7945             this.el.on('keydown', this.checkTab, this);\r
7946         }\r
7947     },\r
7948 \r
7949     // private\r
7950     checkTab: function(e){\r
7951         if (e.getKey() == e.TAB) {\r
7952             this.triggerBlur();\r
7953         }\r
7954     },\r
7955 \r
7956     // private\r
7957     mimicBlur: function(e){\r
7958         if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {\r
7959             this.triggerBlur();\r
7960         }\r
7961     },\r
7962 \r
7963     // private\r
7964     triggerBlur: function(){\r
7965         this.mimicing = false;\r
7966         Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);\r
7967         this.el.un("keydown", this.checkTab, this);\r
7968         this.field.beforeBlur();\r
7969         this.wrap.removeClass('x-trigger-wrap-focus');\r
7970         this.field.onBlur.call(this.field);\r
7971     },\r
7972 \r
7973     initTrigger: function(){\r
7974         this.trigger.addClassOnOver('x-form-trigger-over');\r
7975         this.trigger.addClassOnClick('x-form-trigger-click');\r
7976     },\r
7977 \r
7978     initSpinner: function(){\r
7979         this.field.addEvents({\r
7980             'spin': true,\r
7981             'spinup': true,\r
7982             'spindown': true\r
7983         });\r
7984 \r
7985         this.keyNav = new Ext.KeyNav(this.el, {\r
7986             "up": function(e){\r
7987                 e.preventDefault();\r
7988                 this.onSpinUp();\r
7989             },\r
7990 \r
7991             "down": function(e){\r
7992                 e.preventDefault();\r
7993                 this.onSpinDown();\r
7994             },\r
7995 \r
7996             "pageUp": function(e){\r
7997                 e.preventDefault();\r
7998                 this.onSpinUpAlternate();\r
7999             },\r
8000 \r
8001             "pageDown": function(e){\r
8002                 e.preventDefault();\r
8003                 this.onSpinDownAlternate();\r
8004             },\r
8005 \r
8006             scope: this\r
8007         });\r
8008 \r
8009         this.repeater = new Ext.util.ClickRepeater(this.trigger, {\r
8010             accelerate: this.accelerate\r
8011         });\r
8012         this.field.mon(this.repeater, "click", this.onTriggerClick, this, {\r
8013             preventDefault: true\r
8014         });\r
8015 \r
8016         this.field.mon(this.trigger, {\r
8017             mouseover: this.onMouseOver,\r
8018             mouseout: this.onMouseOut,\r
8019             mousemove: this.onMouseMove,\r
8020             mousedown: this.onMouseDown,\r
8021             mouseup: this.onMouseUp,\r
8022             scope: this,\r
8023             preventDefault: true\r
8024         });\r
8025 \r
8026         this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);\r
8027 \r
8028         this.dd.setXConstraint(0, 0, 10)\r
8029         this.dd.setYConstraint(1500, 1500, 10);\r
8030         this.dd.endDrag = this.endDrag.createDelegate(this);\r
8031         this.dd.startDrag = this.startDrag.createDelegate(this);\r
8032         this.dd.onDrag = this.onDrag.createDelegate(this);\r
8033     },\r
8034 \r
8035     onMouseOver: function(){\r
8036         if (this.disabled) {\r
8037             return;\r
8038         }\r
8039         var middle = this.getMiddle();\r
8040         this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';\r
8041         this.trigger.addClass(this.tmpHoverClass);\r
8042     },\r
8043 \r
8044     //private\r
8045     onMouseOut: function(){\r
8046         this.trigger.removeClass(this.tmpHoverClass);\r
8047     },\r
8048 \r
8049     //private\r
8050     onMouseMove: function(){\r
8051         if (this.disabled) {\r
8052             return;\r
8053         }\r
8054         var middle = this.getMiddle();\r
8055         if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||\r
8056         ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {\r
8057         }\r
8058     },\r
8059 \r
8060     //private\r
8061     onMouseDown: function(){\r
8062         if (this.disabled) {\r
8063             return;\r
8064         }\r
8065         var middle = this.getMiddle();\r
8066         this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';\r
8067         this.trigger.addClass(this.tmpClickClass);\r
8068     },\r
8069 \r
8070     //private\r
8071     onMouseUp: function(){\r
8072         this.trigger.removeClass(this.tmpClickClass);\r
8073     },\r
8074 \r
8075     //private\r
8076     onTriggerClick: function(){\r
8077         if (this.disabled || this.el.dom.readOnly) {\r
8078             return;\r
8079         }\r
8080         var middle = this.getMiddle();\r
8081         var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';\r
8082         this['onSpin' + ud]();\r
8083     },\r
8084 \r
8085     //private\r
8086     getMiddle: function(){\r
8087         var t = this.trigger.getTop();\r
8088         var h = this.trigger.getHeight();\r
8089         var middle = t + (h / 2);\r
8090         return middle;\r
8091     },\r
8092 \r
8093     //private\r
8094     //checks if control is allowed to spin\r
8095     isSpinnable: function(){\r
8096         if (this.disabled || this.el.dom.readOnly) {\r
8097             Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly\r
8098             return false;\r
8099         }\r
8100         return true;\r
8101     },\r
8102 \r
8103     handleMouseWheel: function(e){\r
8104         //disable scrolling when not focused\r
8105         if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {\r
8106             return;\r
8107         }\r
8108 \r
8109         var delta = e.getWheelDelta();\r
8110         if (delta > 0) {\r
8111             this.onSpinUp();\r
8112             e.stopEvent();\r
8113         }\r
8114         else\r
8115             if (delta < 0) {\r
8116                 this.onSpinDown();\r
8117                 e.stopEvent();\r
8118             }\r
8119     },\r
8120 \r
8121     //private\r
8122     startDrag: function(){\r
8123         this.proxy.show();\r
8124         this._previousY = Ext.fly(this.dd.getDragEl()).getTop();\r
8125     },\r
8126 \r
8127     //private\r
8128     endDrag: function(){\r
8129         this.proxy.hide();\r
8130     },\r
8131 \r
8132     //private\r
8133     onDrag: function(){\r
8134         if (this.disabled) {\r
8135             return;\r
8136         }\r
8137         var y = Ext.fly(this.dd.getDragEl()).getTop();\r
8138         var ud = '';\r
8139 \r
8140         if (this._previousY > y) {\r
8141             ud = 'Up';\r
8142         } //up\r
8143         if (this._previousY < y) {\r
8144             ud = 'Down';\r
8145         } //down\r
8146         if (ud != '') {\r
8147             this['onSpin' + ud]();\r
8148         }\r
8149 \r
8150         this._previousY = y;\r
8151     },\r
8152 \r
8153     //private\r
8154     onSpinUp: function(){\r
8155         if (this.isSpinnable() == false) {\r
8156             return;\r
8157         }\r
8158         if (Ext.EventObject.shiftKey == true) {\r
8159             this.onSpinUpAlternate();\r
8160             return;\r
8161         }\r
8162         else {\r
8163             this.spin(false, false);\r
8164         }\r
8165         this.field.fireEvent("spin", this);\r
8166         this.field.fireEvent("spinup", this);\r
8167     },\r
8168 \r
8169     //private\r
8170     onSpinDown: function(){\r
8171         if (this.isSpinnable() == false) {\r
8172             return;\r
8173         }\r
8174         if (Ext.EventObject.shiftKey == true) {\r
8175             this.onSpinDownAlternate();\r
8176             return;\r
8177         }\r
8178         else {\r
8179             this.spin(true, false);\r
8180         }\r
8181         this.field.fireEvent("spin", this);\r
8182         this.field.fireEvent("spindown", this);\r
8183     },\r
8184 \r
8185     //private\r
8186     onSpinUpAlternate: function(){\r
8187         if (this.isSpinnable() == false) {\r
8188             return;\r
8189         }\r
8190         this.spin(false, true);\r
8191         this.field.fireEvent("spin", this);\r
8192         this.field.fireEvent("spinup", this);\r
8193     },\r
8194 \r
8195     //private\r
8196     onSpinDownAlternate: function(){\r
8197         if (this.isSpinnable() == false) {\r
8198             return;\r
8199         }\r
8200         this.spin(true, true);\r
8201         this.field.fireEvent("spin", this);\r
8202         this.field.fireEvent("spindown", this);\r
8203     },\r
8204 \r
8205     spin: function(down, alternate){\r
8206         var v = parseFloat(this.field.getValue());\r
8207         var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;\r
8208         (down == true) ? v -= incr : v += incr;\r
8209 \r
8210         v = (isNaN(v)) ? this.defaultValue : v;\r
8211         v = this.fixBoundries(v);\r
8212         this.field.setRawValue(v);\r
8213     },\r
8214 \r
8215     fixBoundries: function(value){\r
8216         var v = value;\r
8217 \r
8218         if (this.field.minValue != undefined && v < this.field.minValue) {\r
8219             v = this.field.minValue;\r
8220         }\r
8221         if (this.field.maxValue != undefined && v > this.field.maxValue) {\r
8222             v = this.field.maxValue;\r
8223         }\r
8224 \r
8225         return this.fixPrecision(v);\r
8226     },\r
8227 \r
8228     // private\r
8229     fixPrecision: function(value){\r
8230         var nan = isNaN(value);\r
8231         if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {\r
8232             return nan ? '' : value;\r
8233         }\r
8234         return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));\r
8235     },\r
8236 \r
8237     doDestroy: function(){\r
8238         if (this.trigger) {\r
8239             this.trigger.remove();\r
8240         }\r
8241         if (this.wrap) {\r
8242             this.wrap.remove();\r
8243             delete this.field.wrap;\r
8244         }\r
8245 \r
8246         if (this.splitter) {\r
8247             this.splitter.remove();\r
8248         }\r
8249 \r
8250         if (this.dd) {\r
8251             this.dd.unreg();\r
8252             this.dd = null;\r
8253         }\r
8254 \r
8255         if (this.proxy) {\r
8256             this.proxy.remove();\r
8257         }\r
8258 \r
8259         if (this.repeater) {\r
8260             this.repeater.purgeListeners();\r
8261         }\r
8262     }\r
8263 });\r
8264 \r
8265 //backwards compat\r
8266 Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){\r
8267     Ext.apply(this, config);\r
8268 }\r
8269 Ext.ux.Spotlight.prototype = {\r
8270     active : false,\r
8271     animate : true,\r
8272     duration: .25,\r
8273     easing:'easeNone',\r
8274 \r
8275     // private\r
8276     animated : false,\r
8277 \r
8278     createElements : function(){\r
8279         var bd = Ext.getBody();\r
8280 \r
8281         this.right = bd.createChild({cls:'x-spotlight'});\r
8282         this.left = bd.createChild({cls:'x-spotlight'});\r
8283         this.top = bd.createChild({cls:'x-spotlight'});\r
8284         this.bottom = bd.createChild({cls:'x-spotlight'});\r
8285 \r
8286         this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);\r
8287     },\r
8288 \r
8289     show : function(el, callback, scope){\r
8290         if(this.animated){\r
8291             this.show.defer(50, this, [el, callback, scope]);\r
8292             return;\r
8293         }\r
8294         this.el = Ext.get(el);\r
8295         if(!this.right){\r
8296             this.createElements();\r
8297         }\r
8298         if(!this.active){\r
8299             this.all.setDisplayed('');\r
8300             this.applyBounds(true, false);\r
8301             this.active = true;\r
8302             Ext.EventManager.onWindowResize(this.syncSize, this);\r
8303             this.applyBounds(false, this.animate, false, callback, scope);\r
8304         }else{\r
8305             this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous\r
8306         }\r
8307     },\r
8308 \r
8309     hide : function(callback, scope){\r
8310         if(this.animated){\r
8311             this.hide.defer(50, this, [callback, scope]);\r
8312             return;\r
8313         }\r
8314         Ext.EventManager.removeResizeListener(this.syncSize, this);\r
8315         this.applyBounds(true, this.animate, true, callback, scope);\r
8316     },\r
8317 \r
8318     doHide : function(){\r
8319         this.active = false;\r
8320         this.all.setDisplayed(false);\r
8321     },\r
8322 \r
8323     syncSize : function(){\r
8324         this.applyBounds(false, false);\r
8325     },\r
8326 \r
8327     applyBounds : function(basePts, anim, doHide, callback, scope){\r
8328 \r
8329         var rg = this.el.getRegion();\r
8330 \r
8331         var dw = Ext.lib.Dom.getViewWidth(true);\r
8332         var dh = Ext.lib.Dom.getViewHeight(true);\r
8333 \r
8334         var c = 0, cb = false;\r
8335         if(anim){\r
8336             cb = {\r
8337                 callback: function(){\r
8338                     c++;\r
8339                     if(c == 4){\r
8340                         this.animated = false;\r
8341                         if(doHide){\r
8342                             this.doHide();\r
8343                         }\r
8344                         Ext.callback(callback, scope, [this]);\r
8345                     }\r
8346                 },\r
8347                 scope: this,\r
8348                 duration: this.duration,\r
8349                 easing: this.easing\r
8350             };\r
8351             this.animated = true;\r
8352         }\r
8353 \r
8354         this.right.setBounds(\r
8355                 rg.right,\r
8356                 basePts ? dh : rg.top,\r
8357                 dw - rg.right,\r
8358                 basePts ? 0 : (dh - rg.top),\r
8359                 cb);\r
8360 \r
8361         this.left.setBounds(\r
8362                 0,\r
8363                 0,\r
8364                 rg.left,\r
8365                 basePts ? 0 : rg.bottom,\r
8366                 cb);\r
8367 \r
8368         this.top.setBounds(\r
8369                 basePts ? dw : rg.left,\r
8370                 0,\r
8371                 basePts ? 0 : dw - rg.left,\r
8372                 rg.top,\r
8373                 cb);\r
8374 \r
8375         this.bottom.setBounds(\r
8376                 0,\r
8377                 rg.bottom,\r
8378                 basePts ? 0 : rg.right,\r
8379                 dh - rg.bottom,\r
8380                 cb);\r
8381 \r
8382         if(!anim){\r
8383             if(doHide){\r
8384                 this.doHide();\r
8385             }\r
8386             if(callback){\r
8387                 Ext.callback(callback, scope, [this]);\r
8388             }\r
8389         }\r
8390     },\r
8391 \r
8392     destroy : function(){\r
8393         this.doHide();\r
8394         Ext.destroy(\r
8395             this.right,\r
8396             this.left,\r
8397             this.top,\r
8398             this.bottom);\r
8399         delete this.el;\r
8400         delete this.all;\r
8401     }\r
8402 };\r
8403 \r
8404 //backwards compat\r
8405 Ext.Spotlight = Ext.ux.Spotlight;/**
8406  * @class Ext.ux.StatusBar
8407  * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}.  In addition to
8408  * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
8409  * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
8410  * status text and icon.  You can also indicate that something is processing using the {@link #showBusy} method.</p>
8411  * <pre><code>
8412 new Ext.Panel({
8413     title: 'StatusBar',
8414     // etc.
8415     bbar: new Ext.ux.StatusBar({
8416         id: 'my-status',
8417
8418         // defaults to use when the status is cleared:
8419         defaultText: 'Default status text',
8420         defaultIconCls: 'default-icon',
8421
8422         // values to set initially:
8423         text: 'Ready',
8424         iconCls: 'ready-icon',
8425
8426         // any standard Toolbar items:
8427         items: [{
8428             text: 'A Button'
8429         }, '-', 'Plain Text']
8430     })
8431 });
8432
8433 // Update the status bar later in code:
8434 var sb = Ext.getCmp('my-status');
8435 sb.setStatus({
8436     text: 'OK',
8437     iconCls: 'ok-icon',
8438     clear: true // auto-clear after a set interval
8439 });
8440
8441 // Set the status bar to show that something is processing:
8442 sb.showBusy();
8443
8444 // processing....
8445
8446 sb.clearStatus(); // once completeed
8447 </code></pre>
8448  * @extends Ext.Toolbar
8449  * @constructor
8450  * Creates a new StatusBar
8451  * @param {Object/Array} config A config object
8452  */
8453 Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
8454     /**
8455      * @cfg {String} statusAlign
8456      * The alignment of the status element within the overall StatusBar layout.  When the StatusBar is rendered,
8457      * it creates an internal div containing the status text and icon.  Any additional Toolbar items added in the
8458      * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
8459      * rendered, in added order, to the opposite side.  The status element is greedy, so it will automatically
8460      * expand to take up all sapce left over by any other items.  Example usage:
8461      * <pre><code>
8462 // Create a left-aligned status bar containing a button,
8463 // separator and text item that will be right-aligned (default):
8464 new Ext.Panel({
8465     title: 'StatusBar',
8466     // etc.
8467     bbar: new Ext.ux.StatusBar({
8468         defaultText: 'Default status text',
8469         id: 'status-id',
8470         items: [{
8471             text: 'A Button'
8472         }, '-', 'Plain Text']
8473     })
8474 });
8475
8476 // By adding the statusAlign config, this will create the
8477 // exact same toolbar, except the status and toolbar item
8478 // layout will be reversed from the previous example:
8479 new Ext.Panel({
8480     title: 'StatusBar',
8481     // etc.
8482     bbar: new Ext.ux.StatusBar({
8483         defaultText: 'Default status text',
8484         id: 'status-id',
8485         statusAlign: 'right',
8486         items: [{
8487             text: 'A Button'
8488         }, '-', 'Plain Text']
8489     })
8490 });
8491 </code></pre>
8492      */
8493     /**
8494      * @cfg {String} defaultText
8495      * The default {@link #text} value.  This will be used anytime the status bar is cleared with the
8496      * <tt>useDefaults:true</tt> option (defaults to '').
8497      */
8498     /**
8499      * @cfg {String} defaultIconCls
8500      * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
8501      * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
8502      */
8503     /**
8504      * @cfg {String} text
8505      * A string that will be <b>initially</b> set as the status message.  This string
8506      * will be set as innerHTML (html tags are accepted) for the toolbar item.
8507      * If not specified, the value set for <code>{@link #defaultText}</code>
8508      * will be used.
8509      */
8510     /**
8511      * @cfg {String} iconCls
8512      * A CSS class that will be <b>initially</b> set as the status bar icon and is
8513      * expected to provide a background image (defaults to '').
8514      * Example usage:<pre><code>
8515 // Example CSS rule:
8516 .x-statusbar .x-status-custom {
8517     padding-left: 25px;
8518     background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
8519 }
8520
8521 // Setting a default icon:
8522 var sb = new Ext.ux.StatusBar({
8523     defaultIconCls: 'x-status-custom'
8524 });
8525
8526 // Changing the icon:
8527 sb.setStatus({
8528     text: 'New status',
8529     iconCls: 'x-status-custom'
8530 });
8531 </code></pre>
8532      */
8533
8534     /**
8535      * @cfg {String} cls
8536      * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
8537      */
8538     cls : 'x-statusbar',
8539     /**
8540      * @cfg {String} busyIconCls
8541      * The default <code>{@link #iconCls}</code> applied when calling
8542      * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
8543      * It can be overridden at any time by passing the <code>iconCls</code>
8544      * argument into <code>{@link #showBusy}</code>.
8545      */
8546     busyIconCls : 'x-status-busy',
8547     /**
8548      * @cfg {String} busyText
8549      * The default <code>{@link #text}</code> applied when calling
8550      * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
8551      * It can be overridden at any time by passing the <code>text</code>
8552      * argument into <code>{@link #showBusy}</code>.
8553      */
8554     busyText : 'Loading...',
8555     /**
8556      * @cfg {Number} autoClear
8557      * The number of milliseconds to wait after setting the status via
8558      * <code>{@link #setStatus}</code> before automatically clearing the status
8559      * text and icon (defaults to <tt>5000</tt>).  Note that this only applies
8560      * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
8561      * since that is the only way to defer clearing the status.  This can
8562      * be overridden by specifying a different <tt>wait</tt> value in
8563      * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
8564      * always clear the status bar immediately and ignore this value.
8565      */
8566     autoClear : 5000,
8567
8568     /**
8569      * @cfg {String} emptyText
8570      * The text string to use if no text has been set.  Defaults to
8571      * <tt>'&nbsp;'</tt>).  If there are no other items in the toolbar using
8572      * an empty string (<tt>''</tt>) for this value would end up in the toolbar
8573      * height collapsing since the empty string will not maintain the toolbar
8574      * height.  Use <tt>''</tt> if the toolbar should collapse in height
8575      * vertically when no text is specified and there are no other items in
8576      * the toolbar.
8577      */
8578     emptyText : '&nbsp;',
8579
8580     // private
8581     activeThreadId : 0,
8582
8583     // private
8584     initComponent : function(){
8585         if(this.statusAlign=='right'){
8586             this.cls += ' x-status-right';
8587         }
8588         Ext.ux.StatusBar.superclass.initComponent.call(this);
8589     },
8590
8591     // private
8592     afterRender : function(){
8593         Ext.ux.StatusBar.superclass.afterRender.call(this);
8594
8595         var right = this.statusAlign == 'right';
8596         this.currIconCls = this.iconCls || this.defaultIconCls;
8597         this.statusEl = new Ext.Toolbar.TextItem({
8598             cls: 'x-status-text ' + (this.currIconCls || ''),
8599             text: this.text || this.defaultText || ''
8600         });
8601
8602         if(right){
8603             this.add('->');
8604             this.add(this.statusEl);
8605         }else{
8606             this.insert(0, this.statusEl);
8607             this.insert(1, '->');
8608         }
8609
8610 //         this.statusEl = td.createChild({
8611 //             cls: 'x-status-text ' + (this.iconCls || this.defaultIconCls || ''),
8612 //             html: this.text || this.defaultText || ''
8613 //         });
8614 //         this.statusEl.unselectable();
8615
8616 //         this.spacerEl = td.insertSibling({
8617 //             tag: 'td',
8618 //             style: 'width:100%',
8619 //             cn: [{cls:'ytb-spacer'}]
8620 //         }, right ? 'before' : 'after');
8621     },
8622
8623     /**
8624      * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
8625      * status that was set after a specified interval.
8626      * @param {Object/String} config A config object specifying what status to set, or a string assumed
8627      * to be the status text (and all other options are defaulted as explained below). A config
8628      * object containing any or all of the following properties can be passed:<ul>
8629      * <li><tt>text</tt> {String} : (optional) The status text to display.  If not specified, any current
8630      * status text will remain unchanged.</li>
8631      * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
8632      * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
8633      * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
8634      * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
8635      * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
8636      * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
8637      * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
8638      * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
8639      * All other options will be defaulted as with the boolean option.  To customize any other options,
8640      * you can pass an object in the format:<ul>
8641      *    <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
8642      *    (defaults to {@link #autoClear}).</li>
8643      *    <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
8644      *    executes (defaults to true which fades the status out).</li>
8645      *    <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
8646      *    (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
8647      * </ul></li></ul>
8648      * Example usage:<pre><code>
8649 // Simple call to update the text
8650 statusBar.setStatus('New status');
8651
8652 // Set the status and icon, auto-clearing with default options:
8653 statusBar.setStatus({
8654     text: 'New status',
8655     iconCls: 'x-status-custom',
8656     clear: true
8657 });
8658
8659 // Auto-clear with custom options:
8660 statusBar.setStatus({
8661     text: 'New status',
8662     iconCls: 'x-status-custom',
8663     clear: {
8664         wait: 8000,
8665         anim: false,
8666         useDefaults: false
8667     }
8668 });
8669 </code></pre>
8670      * @return {Ext.ux.StatusBar} this
8671      */
8672     setStatus : function(o){
8673         o = o || {};
8674
8675         if(typeof o == 'string'){
8676             o = {text:o};
8677         }
8678         if(o.text !== undefined){
8679             this.setText(o.text);
8680         }
8681         if(o.iconCls !== undefined){
8682             this.setIcon(o.iconCls);
8683         }
8684
8685         if(o.clear){
8686             var c = o.clear,
8687                 wait = this.autoClear,
8688                 defaults = {useDefaults: true, anim: true};
8689
8690             if(typeof c == 'object'){
8691                 c = Ext.applyIf(c, defaults);
8692                 if(c.wait){
8693                     wait = c.wait;
8694                 }
8695             }else if(typeof c == 'number'){
8696                 wait = c;
8697                 c = defaults;
8698             }else if(typeof c == 'boolean'){
8699                 c = defaults;
8700             }
8701
8702             c.threadId = this.activeThreadId;
8703             this.clearStatus.defer(wait, this, [c]);
8704         }
8705         return this;
8706     },
8707
8708     /**
8709      * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
8710      * @param {Object} config (optional) A config object containing any or all of the following properties.  If this
8711      * object is not specified the status will be cleared using the defaults below:<ul>
8712      * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
8713      * to false which clears immediately).</li>
8714      * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
8715      * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
8716      * </ul>
8717      * @return {Ext.ux.StatusBar} this
8718      */
8719     clearStatus : function(o){
8720         o = o || {};
8721
8722         if(o.threadId && o.threadId !== this.activeThreadId){
8723             // this means the current call was made internally, but a newer
8724             // thread has set a message since this call was deferred.  Since
8725             // we don't want to overwrite a newer message just ignore.
8726             return this;
8727         }
8728
8729         var text = o.useDefaults ? this.defaultText : this.emptyText,
8730             iconCls = o.useDefaults ? (this.defaultIconCls ? this.defaultIconCls : '') : '';
8731
8732         if(o.anim){
8733             // animate the statusEl Ext.Element
8734             this.statusEl.el.fadeOut({
8735                 remove: false,
8736                 useDisplay: true,
8737                 scope: this,
8738                 callback: function(){
8739                     this.setStatus({
8740                             text: text,
8741                             iconCls: iconCls
8742                         });
8743
8744                     this.statusEl.el.show();
8745                 }
8746             });
8747         }else{
8748             // hide/show the el to avoid jumpy text or icon
8749             this.statusEl.hide();
8750                 this.setStatus({
8751                     text: text,
8752                     iconCls: iconCls
8753                 });
8754             this.statusEl.show();
8755         }
8756         return this;
8757     },
8758
8759     /**
8760      * Convenience method for setting the status text directly.  For more flexible options see {@link #setStatus}.
8761      * @param {String} text (optional) The text to set (defaults to '')
8762      * @return {Ext.ux.StatusBar} this
8763      */
8764     setText : function(text){
8765         this.activeThreadId++;
8766         this.text = text || '';
8767         if(this.rendered){
8768             this.statusEl.setText(this.text);
8769         }
8770         return this;
8771     },
8772
8773     /**
8774      * Returns the current status text.
8775      * @return {String} The status text
8776      */
8777     getText : function(){
8778         return this.text;
8779     },
8780
8781     /**
8782      * Convenience method for setting the status icon directly.  For more flexible options see {@link #setStatus}.
8783      * See {@link #iconCls} for complete details about customizing the icon.
8784      * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
8785      * @return {Ext.ux.StatusBar} this
8786      */
8787     setIcon : function(cls){
8788         this.activeThreadId++;
8789         cls = cls || '';
8790
8791         if(this.rendered){
8792                 if(this.currIconCls){
8793                     this.statusEl.removeClass(this.currIconCls);
8794                     this.currIconCls = null;
8795                 }
8796                 if(cls.length > 0){
8797                     this.statusEl.addClass(cls);
8798                     this.currIconCls = cls;
8799                 }
8800         }else{
8801             this.currIconCls = cls;
8802         }
8803         return this;
8804     },
8805
8806     /**
8807      * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
8808      * a "busy" state, usually for loading or processing activities.
8809      * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
8810      * string to use as the status text (in which case all other options for setStatus will be defaulted).  Use the
8811      * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
8812      * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
8813      * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
8814      * @return {Ext.ux.StatusBar} this
8815      */
8816     showBusy : function(o){
8817         if(typeof o == 'string'){
8818             o = {text:o};
8819         }
8820         o = Ext.applyIf(o || {}, {
8821             text: this.busyText,
8822             iconCls: this.busyIconCls
8823         });
8824         return this.setStatus(o);
8825     }
8826 });
8827 Ext.reg('statusbar', Ext.ux.StatusBar);
8828 /**\r
8829  * @class Ext.ux.TabCloseMenu\r
8830  * @extends Object \r
8831  * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.\r
8832  * \r
8833  * @ptype tabclosemenu\r
8834  */\r
8835 Ext.ux.TabCloseMenu = function(){\r
8836     var tabs, menu, ctxItem;\r
8837     this.init = function(tp){\r
8838         tabs = tp;\r
8839         tabs.on('contextmenu', onContextMenu);\r
8840     };\r
8841 \r
8842     function onContextMenu(ts, item, e){\r
8843         if(!menu){ // create context menu on first right click\r
8844             menu = new Ext.menu.Menu({            \r
8845             items: [{\r
8846                 id: tabs.id + '-close',\r
8847                 text: 'Close Tab',\r
8848                 handler : function(){\r
8849                     tabs.remove(ctxItem);\r
8850                 }\r
8851             },{\r
8852                 id: tabs.id + '-close-others',\r
8853                 text: 'Close Other Tabs',\r
8854                 handler : function(){\r
8855                     tabs.items.each(function(item){\r
8856                         if(item.closable && item != ctxItem){\r
8857                             tabs.remove(item);\r
8858                         }\r
8859                     });\r
8860                 }\r
8861             }]});\r
8862         }\r
8863         ctxItem = item;\r
8864         var items = menu.items;\r
8865         items.get(tabs.id + '-close').setDisabled(!item.closable);\r
8866         var disableOthers = true;\r
8867         tabs.items.each(function(){\r
8868             if(this != item && this.closable){\r
8869                 disableOthers = false;\r
8870                 return false;\r
8871             }\r
8872         });\r
8873         items.get(tabs.id + '-close-others').setDisabled(disableOthers);\r
8874         e.stopEvent();\r
8875         menu.showAt(e.getPoint());\r
8876     }\r
8877 };\r
8878 \r
8879 Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);\r
8880 Ext.ns('Ext.ux.grid');
8881
8882 /**
8883  * @class Ext.ux.grid.TableGrid
8884  * @extends Ext.grid.GridPanel
8885  * A Grid which creates itself from an existing HTML table element.
8886  * @history
8887  * 2007-03-01 Original version by Nige "Animal" White
8888  * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor
8889  * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
8890  * The table MUST have some type of size defined for the grid to fill. The container will be
8891  * automatically set to position relative if it isn't already.
8892  * @param {Object} config A config object that sets properties on this grid and has two additional (optional)
8893  * properties: fields and columns which allow for customizing data fields and columns for this grid.
8894  */
8895 Ext.ux.grid.TableGrid = function(table, config){
8896     config = config ||
8897     {};
8898     Ext.apply(this, config);
8899     var cf = config.fields || [], ch = config.columns || [];
8900     table = Ext.get(table);
8901     
8902     var ct = table.insertSibling();
8903     
8904     var fields = [], cols = [];
8905     var headers = table.query("thead th");
8906     for (var i = 0, h; h = headers[i]; i++) {
8907         var text = h.innerHTML;
8908         var name = 'tcol-' + i;
8909         
8910         fields.push(Ext.applyIf(cf[i] ||
8911         {}, {
8912             name: name,
8913             mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
8914         }));
8915         
8916         cols.push(Ext.applyIf(ch[i] ||
8917         {}, {
8918             'header': text,
8919             'dataIndex': name,
8920             'width': h.offsetWidth,
8921             'tooltip': h.title,
8922             'sortable': true
8923         }));
8924     }
8925     
8926     var ds = new Ext.data.Store({
8927         reader: new Ext.data.XmlReader({
8928             record: 'tbody tr'
8929         }, fields)
8930     });
8931     
8932     ds.loadData(table.dom);
8933     
8934     var cm = new Ext.grid.ColumnModel(cols);
8935     
8936     if (config.width || config.height) {
8937         ct.setSize(config.width || 'auto', config.height || 'auto');
8938     }
8939     else {
8940         ct.setWidth(table.getWidth());
8941     }
8942     
8943     if (config.remove !== false) {
8944         table.remove();
8945     }
8946     
8947     Ext.applyIf(this, {
8948         'ds': ds,
8949         'cm': cm,
8950         'sm': new Ext.grid.RowSelectionModel(),
8951         autoHeight: true,
8952         autoWidth: false
8953     });
8954     Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {});
8955 };
8956
8957 Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
8958
8959 //backwards compat
8960 Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
8961 Ext.ns('Ext.ux');
8962 /**
8963  * @class Ext.ux.TabScrollerMenu
8964  * @extends Object 
8965  * Plugin (ptype = 'tabscrollermenu') for adding a tab scroller menu to tabs.
8966  * @constructor 
8967  * @param {Object} config Configuration options
8968  * @ptype tabscrollermenu
8969  */
8970 Ext.ux.TabScrollerMenu =  Ext.extend(Object, {
8971     /**
8972      * @cfg {Number} pageSize How many items to allow per submenu.
8973      */
8974         pageSize       : 10,
8975     /**
8976      * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
8977      */
8978         maxText        : 15,
8979     /**
8980      * @cfg {String} menuPrefixText Text to prefix the submenus.
8981      */    
8982         menuPrefixText : 'Items',
8983         constructor    : function(config) {
8984                 config = config || {};
8985                 Ext.apply(this, config);
8986         },
8987     //private
8988         init : function(tabPanel) {
8989                 Ext.apply(tabPanel, this.parentOverrides);
8990                 
8991                 tabPanel.tabScrollerMenu = this;
8992                 var thisRef = this;
8993                 
8994                 tabPanel.on({
8995                         render : {
8996                                 scope  : tabPanel,
8997                                 single : true,
8998                                 fn     : function() { 
8999                                         var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
9000                                         tabPanel.createScrollers = newFn;
9001                                 }
9002                         }
9003                 });
9004         },
9005         // private && sequeneced
9006         createPanelsMenu : function() {
9007                 var h = this.stripWrap.dom.offsetHeight;
9008                 
9009                 //move the right menu item to the left 18px
9010                 var rtScrBtn = this.header.dom.firstChild;
9011                 Ext.fly(rtScrBtn).applyStyles({
9012                         right : '18px'
9013                 });
9014                 
9015                 var stripWrap = Ext.get(this.strip.dom.parentNode);
9016                 stripWrap.applyStyles({
9017                          'margin-right' : '36px'
9018                 });
9019                 
9020                 // Add the new righthand menu
9021                 var scrollMenu = this.header.insertFirst({
9022                         cls:'x-tab-tabmenu-right'
9023                 });
9024                 scrollMenu.setHeight(h);
9025                 scrollMenu.addClassOnOver('x-tab-tabmenu-over');
9026                 scrollMenu.on('click', this.showTabsMenu, this);        
9027                 
9028                 this.scrollLeft.show = this.scrollLeft.show.createSequence(function() {
9029                         scrollMenu.show();                                                                                                                                               
9030                 });
9031                 
9032                 this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() {
9033                         scrollMenu.hide();                                                              
9034                 });
9035                 
9036         },
9037     /**
9038      * Returns an the current page size (this.pageSize);
9039      * @return {Number} this.pageSize The current page size.
9040      */
9041         getPageSize : function() {
9042                 return this.pageSize;
9043         },
9044     /**
9045      * Sets the number of menu items per submenu "page size".
9046      * @param {Number} pageSize The page size
9047      */
9048     setPageSize : function(pageSize) {
9049                 this.pageSize = pageSize;
9050         },
9051     /**
9052      * Returns the current maxText length;
9053      * @return {Number} this.maxText The current max text length.
9054      */
9055     getMaxText : function() {
9056                 return this.maxText;
9057         },
9058     /**
9059      * Sets the maximum text size for each menu item.
9060      * @param {Number} t The max text per each menu item.
9061      */
9062     setMaxText : function(t) {
9063                 this.maxText = t;
9064         },
9065     /**
9066      * Returns the current menu prefix text String.;
9067      * @return {String} this.menuPrefixText The current menu prefix text.
9068      */
9069         getMenuPrefixText : function() {
9070                 return this.menuPrefixText;
9071         },
9072     /**
9073      * Sets the menu prefix text String.
9074      * @param {String} t The menu prefix text.
9075      */    
9076         setMenuPrefixText : function(t) {
9077                 this.menuPrefixText = t;
9078         },
9079         // private && applied to the tab panel itself.
9080         parentOverrides : {
9081                 // all execute within the scope of the tab panel
9082                 // private      
9083                 showTabsMenu : function(e) {            
9084                         if  (this.tabsMenu) {
9085                                 this.tabsMenu.destroy();
9086                 this.un('destroy', this.tabsMenu.destroy, this.tabsMenu);
9087                 this.tabsMenu = null;
9088                         }
9089             this.tabsMenu =  new Ext.menu.Menu();
9090             this.on('destroy', this.tabsMenu.destroy, this.tabsMenu);
9091
9092             this.generateTabMenuItems();
9093
9094             var target = Ext.get(e.getTarget());
9095                         var xy     = target.getXY();
9096 //
9097                         //Y param + 24 pixels
9098                         xy[1] += 24;
9099                         
9100                         this.tabsMenu.showAt(xy);
9101                 },
9102                 // private      
9103                 generateTabMenuItems : function() {
9104                         var curActive  = this.getActiveTab();
9105                         var totalItems = this.items.getCount();
9106                         var pageSize   = this.tabScrollerMenu.getPageSize();
9107                         
9108                         
9109                         if (totalItems > pageSize)  {
9110                                 var numSubMenus = Math.floor(totalItems / pageSize);
9111                                 var remainder   = totalItems % pageSize;
9112                                 
9113                                 // Loop through all of the items and create submenus in chunks of 10
9114                                 for (var i = 0 ; i < numSubMenus; i++) {
9115                                         var curPage = (i + 1) * pageSize;
9116                                         var menuItems = [];
9117                                         
9118                                         
9119                                         for (var x = 0; x < pageSize; x++) {                            
9120                                                 index = x + curPage - pageSize;
9121                                                 var item = this.items.get(index);
9122                                                 menuItems.push(this.autoGenMenuItem(item));
9123                                         }
9124                                         
9125                                         this.tabsMenu.add({
9126                                                 text : this.tabScrollerMenu.getMenuPrefixText() + ' '  + (curPage - pageSize + 1) + ' - ' + curPage,
9127                                                 menu : menuItems
9128                                         });
9129                                         
9130                                 }
9131                                 // remaining items
9132                                 if (remainder > 0) {
9133                                         var start = numSubMenus * pageSize;
9134                                         menuItems = [];
9135                                         for (var i = start ; i < totalItems; i ++ ) {                                   
9136                                                 var item = this.items.get(i);
9137                                                 menuItems.push(this.autoGenMenuItem(item));
9138                                         }
9139                                         
9140                                         this.tabsMenu.add({
9141                                                 text : this.tabScrollerMenu.menuPrefixText  + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
9142                                                 menu : menuItems
9143                                         });
9144
9145                                 }
9146                         }
9147                         else {
9148                                 this.items.each(function(item) {
9149                                         if (item.id != curActive.id && ! item.hidden) {
9150                                                 menuItems.push(this.autoGenMenuItem(item));
9151                                         }
9152                                 }, this);
9153                         }
9154                 },
9155                 // private
9156                 autoGenMenuItem : function(item) {
9157                         var maxText = this.tabScrollerMenu.getMaxText();
9158                         var text    = Ext.util.Format.ellipsis(item.title, maxText);
9159                         
9160                         return {
9161                                 text      : text,
9162                                 handler   : this.showTabFromMenu,
9163                                 scope     : this,
9164                                 disabled  : item.disabled,
9165                                 tabToShow : item,
9166                                 iconCls   : item.iconCls
9167                         }
9168                 
9169                 },
9170                 // private
9171                 showTabFromMenu : function(menuItem) {
9172                         this.setActiveTab(menuItem.tabToShow);
9173                 }       
9174         }       
9175 });
9176
9177 Ext.reg('tabscrollermenu', Ext.ux.TabScrollerMenu);
9178 Ext.ns('Ext.ux.tree');
9179
9180 /**
9181  * @class Ext.ux.tree.XmlTreeLoader
9182  * @extends Ext.tree.TreeLoader
9183  * <p>A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s.
9184  * Any text value included as a text node in the XML will be added to the parent node as an attribute
9185  * called <tt>innerText</tt>.  Also, the tag name of each XML node will be added to the tree node as
9186  * an attribute called <tt>tagName</tt>.</p>
9187  * <p>By default, this class expects that your source XML will provide the necessary attributes on each
9188  * node as expected by the {@link Ext.tree.TreePanel} to display and load properly.  However, you can
9189  * provide your own custom processing of node attributes by overriding the {@link #processNode} method
9190  * and modifying the attributes as needed before they are used to create the associated TreeNode.</p>
9191  * @constructor
9192  * Creates a new XmlTreeloader.
9193  * @param {Object} config A config object containing config properties.
9194  */
9195 Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
9196     /**
9197      * @property  XML_NODE_ELEMENT
9198      * XML element node (value 1, read-only)
9199      * @type Number
9200      */
9201     XML_NODE_ELEMENT : 1,
9202     /**
9203      * @property  XML_NODE_TEXT
9204      * XML text node (value 3, read-only)
9205      * @type Number
9206      */
9207     XML_NODE_TEXT : 3,
9208
9209     // private override
9210     processResponse : function(response, node, callback){
9211         var xmlData = response.responseXML;
9212         var root = xmlData.documentElement || xmlData;
9213
9214         try{
9215             node.beginUpdate();
9216             node.appendChild(this.parseXml(root));
9217             node.endUpdate();
9218
9219             if(typeof callback == "function"){
9220                 callback(this, node);
9221             }
9222         }catch(e){
9223             this.handleFailure(response);
9224         }
9225     },
9226
9227     // private
9228     parseXml : function(node) {
9229         var nodes = [];
9230         Ext.each(node.childNodes, function(n){
9231             if(n.nodeType == this.XML_NODE_ELEMENT){
9232                 var treeNode = this.createNode(n);
9233                 if(n.childNodes.length > 0){
9234                     var child = this.parseXml(n);
9235                     if(typeof child == 'string'){
9236                         treeNode.attributes.innerText = child;
9237                     }else{
9238                         treeNode.appendChild(child);
9239                     }
9240                 }
9241                 nodes.push(treeNode);
9242             }
9243             else if(n.nodeType == this.XML_NODE_TEXT){
9244                 var text = n.nodeValue.trim();
9245                 if(text.length > 0){
9246                     return nodes = text;
9247                 }
9248             }
9249         }, this);
9250
9251         return nodes;
9252     },
9253
9254     // private override
9255     createNode : function(node){
9256         var attr = {
9257             tagName: node.tagName
9258         };
9259
9260         Ext.each(node.attributes, function(a){
9261             attr[a.nodeName] = a.nodeValue;
9262         });
9263
9264         this.processAttributes(attr);
9265
9266         return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
9267     },
9268
9269     /*
9270      * Template method intended to be overridden by subclasses that need to provide
9271      * custom attribute processing prior to the creation of each TreeNode.  This method
9272      * will be passed a config object containing existing TreeNode attribute name/value
9273      * pairs which can be modified as needed directly (no need to return the object).
9274      */
9275     processAttributes: Ext.emptyFn
9276 });
9277
9278 //backwards compat
9279 Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;
9280 /**
9281  * @class Ext.ux.ValidationStatus
9282  * A {@link Ext.StatusBar} plugin that provides automatic error notification when the
9283  * associated form contains validation errors.
9284  * @extends Ext.Component
9285  * @constructor
9286  * Creates a new ValiationStatus plugin
9287  * @param {Object} config A config object
9288  */
9289 Ext.ux.ValidationStatus = Ext.extend(Ext.Component, {
9290     /**
9291      * @cfg {String} errorIconCls
9292      * The {@link #iconCls} value to be applied to the status message when there is a
9293      * validation error. Defaults to <tt>'x-status-error'</tt>.
9294      */
9295     errorIconCls : 'x-status-error',
9296     /**
9297      * @cfg {String} errorListCls
9298      * The css class to be used for the error list when there are validation errors.
9299      * Defaults to <tt>'x-status-error-list'</tt>.
9300      */
9301     errorListCls : 'x-status-error-list',
9302     /**
9303      * @cfg {String} validIconCls
9304      * The {@link #iconCls} value to be applied to the status message when the form
9305      * validates. Defaults to <tt>'x-status-valid'</tt>.
9306      */
9307     validIconCls : 'x-status-valid',
9308     
9309     /**
9310      * @cfg {String} showText
9311      * The {@link #text} value to be applied when there is a form validation error.
9312      * Defaults to <tt>'The form has errors (click for details...)'</tt>.
9313      */
9314     showText : 'The form has errors (click for details...)',
9315     /**
9316      * @cfg {String} showText
9317      * The {@link #text} value to display when the error list is displayed.
9318      * Defaults to <tt>'Click again to hide the error list'</tt>.
9319      */
9320     hideText : 'Click again to hide the error list',
9321     /**
9322      * @cfg {String} submitText
9323      * The {@link #text} value to be applied when the form is being submitted.
9324      * Defaults to <tt>'Saving...'</tt>.
9325      */
9326     submitText : 'Saving...',
9327     
9328     // private
9329     init : function(sb){
9330         sb.on('render', function(){
9331             this.statusBar = sb;
9332             this.monitor = true;
9333             this.errors = new Ext.util.MixedCollection();
9334             this.listAlign = (sb.statusAlign=='right' ? 'br-tr?' : 'bl-tl?');
9335             
9336             if(this.form){
9337                 this.form = Ext.getCmp(this.form).getForm();
9338                 this.startMonitoring();
9339                 this.form.on('beforeaction', function(f, action){
9340                     if(action.type == 'submit'){
9341                         // Ignore monitoring while submitting otherwise the field validation
9342                         // events cause the status message to reset too early
9343                         this.monitor = false;
9344                     }
9345                 }, this);
9346                 var startMonitor = function(){
9347                     this.monitor = true;
9348                 };
9349                 this.form.on('actioncomplete', startMonitor, this);
9350                 this.form.on('actionfailed', startMonitor, this);
9351             }
9352         }, this, {single:true});
9353         sb.on({
9354             scope: this,
9355             afterlayout:{
9356                 single: true,
9357                 fn: function(){
9358                     // Grab the statusEl after the first layout.
9359                     sb.statusEl.getEl().on('click', this.onStatusClick, this, {buffer:200});
9360                 } 
9361             }, 
9362             beforedestroy:{
9363                 single: true,
9364                 fn: this.onDestroy
9365             } 
9366         });
9367     },
9368     
9369     // private
9370     startMonitoring : function(){
9371         this.form.items.each(function(f){
9372             f.on('invalid', this.onFieldValidation, this);
9373             f.on('valid', this.onFieldValidation, this);
9374         }, this);
9375     },
9376     
9377     // private
9378     stopMonitoring : function(){
9379         this.form.items.each(function(f){
9380             f.un('invalid', this.onFieldValidation, this);
9381             f.un('valid', this.onFieldValidation, this);
9382         }, this);
9383     },
9384     
9385     // private
9386     onDestroy : function(){
9387         this.stopMonitoring();
9388         this.statusBar.statusEl.un('click', this.onStatusClick, this);
9389         Ext.ux.ValidationStatus.superclass.onDestroy.call(this);
9390     },
9391     
9392     // private
9393     onFieldValidation : function(f, msg){
9394         if(!this.monitor){
9395             return false;
9396         }
9397         if(msg){
9398             this.errors.add(f.id, {field:f, msg:msg});
9399         }else{
9400             this.errors.removeKey(f.id);
9401         }
9402         this.updateErrorList();
9403         if(this.errors.getCount() > 0){
9404             if(this.statusBar.getText() != this.showText){
9405                 this.statusBar.setStatus({text:this.showText, iconCls:this.errorIconCls});
9406             }
9407         }else{
9408             this.statusBar.clearStatus().setIcon(this.validIconCls);
9409         }
9410     },
9411     
9412     // private
9413     updateErrorList : function(){
9414         if(this.errors.getCount() > 0){
9415                 var msg = '<ul>';
9416                 this.errors.each(function(err){
9417                     msg += ('<li id="x-err-'+ err.field.id +'"><a href="#">' + err.msg + '</a></li>');
9418                 }, this);
9419                 this.getMsgEl().update(msg+'</ul>');
9420         }else{
9421             this.getMsgEl().update('');
9422         }
9423     },
9424     
9425     // private
9426     getMsgEl : function(){
9427         if(!this.msgEl){
9428             this.msgEl = Ext.DomHelper.append(Ext.getBody(), {
9429                 cls: this.errorListCls+' x-hide-offsets'
9430             }, true);
9431             
9432             this.msgEl.on('click', function(e){
9433                 var t = e.getTarget('li', 10, true);
9434                 if(t){
9435                     Ext.getCmp(t.id.split('x-err-')[1]).focus();
9436                     this.hideErrors();
9437                 }
9438             }, this, {stopEvent:true}); // prevent anchor click navigation
9439         }
9440         return this.msgEl;
9441     },
9442     
9443     // private
9444     showErrors : function(){
9445         this.updateErrorList();
9446         this.getMsgEl().alignTo(this.statusBar.getEl(), this.listAlign).slideIn('b', {duration:0.3, easing:'easeOut'});
9447         this.statusBar.setText(this.hideText);
9448         this.form.getEl().on('click', this.hideErrors, this, {single:true}); // hide if the user clicks directly into the form
9449     },
9450     
9451     // private
9452     hideErrors : function(){
9453         var el = this.getMsgEl();
9454         if(el.isVisible()){
9455                 el.slideOut('b', {duration:0.2, easing:'easeIn'});
9456                 this.statusBar.setText(this.showText);
9457         }
9458         this.form.getEl().un('click', this.hideErrors, this);
9459     },
9460     
9461     // private
9462     onStatusClick : function(){
9463         if(this.getMsgEl().isVisible()){
9464             this.hideErrors();
9465         }else if(this.errors.getCount() > 0){
9466             this.showErrors();
9467         }
9468     }
9469 });(function() {    
9470     Ext.override(Ext.list.Column, {
9471         init : function() {            
9472             if(!this.type){
9473                 this.type = "auto";
9474             }
9475
9476             var st = Ext.data.SortTypes;
9477             // named sortTypes are supported, here we look them up
9478             if(typeof this.sortType == "string"){
9479                 this.sortType = st[this.sortType];
9480             }
9481
9482             // set default sortType for strings and dates
9483             if(!this.sortType){
9484                 switch(this.type){
9485                     case "string":
9486                         this.sortType = st.asUCString;
9487                         break;
9488                     case "date":
9489                         this.sortType = st.asDate;
9490                         break;
9491                     default:
9492                         this.sortType = st.none;
9493                 }
9494             }
9495         }
9496     });
9497
9498     Ext.tree.Column = Ext.extend(Ext.list.Column, {});
9499     Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {});
9500     Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {});
9501     Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {});
9502
9503     Ext.reg('tgcolumn', Ext.tree.Column);
9504     Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn);
9505     Ext.reg('tgdatecolumn', Ext.tree.DateColumn);
9506     Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn);
9507 })();
9508 /**
9509  * @class Ext.ux.tree.TreeGridNodeUI
9510  * @extends Ext.tree.TreeNodeUI
9511  */
9512 Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
9513     isTreeGridNodeUI: true,
9514     
9515     renderElements : function(n, a, targetNode, bulkRender){
9516         var t = n.getOwnerTree(),
9517             cols = t.columns,
9518             c = cols[0],
9519             i, buf, len;
9520
9521         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
9522
9523         buf = [
9524              '<tbody class="x-tree-node">',
9525                 '<tr ext:tree-node-id="', n.id ,'" class="x-tree-node-el ', a.cls, '">',
9526                     '<td class="x-treegrid-col">',
9527                         '<span class="x-tree-node-indent">', this.indentMarkup, "</span>",
9528                         '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
9529                         '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon', (a.icon ? " x-tree-node-inline-icon" : ""), (a.iconCls ? " "+a.iconCls : ""), '" unselectable="on">',
9530                         '<a hidefocus="on" class="x-tree-node-anchor" href="', a.href ? a.href : '#', '" tabIndex="1" ',
9531                             a.hrefTarget ? ' target="'+a.hrefTarget+'"' : '', '>',
9532                         '<span unselectable="on">', (c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text), '</span></a>',
9533                     '</td>'
9534         ];
9535
9536         for(i = 1, len = cols.length; i < len; i++){
9537             c = cols[i];
9538             buf.push(
9539                     '<td class="x-treegrid-col ', (c.cls ? c.cls : ''), '">',
9540                         '<div unselectable="on" class="x-treegrid-text"', (c.align ? ' style="text-align: ' + c.align + ';"' : ''), '>',
9541                             (c.tpl ? c.tpl.apply(a) : a[c.dataIndex]),
9542                         '</div>',
9543                     '</td>'
9544             );
9545         }
9546
9547         buf.push(
9548             '</tr><tr class="x-tree-node-ct"><td colspan="', cols.length, '">',
9549             '<table class="x-treegrid-node-ct-table" cellpadding="0" cellspacing="0" style="table-layout: fixed; display: none; width: ', t.innerCt.getWidth() ,'px;"><colgroup>'
9550         );
9551         for(i = 0, len = cols.length; i<len; i++) {
9552             buf.push('<col style="width: ', (cols[i].hidden ? 0 : cols[i].width) ,'px;" />');
9553         }
9554         buf.push('</colgroup></table></td></tr></tbody>');
9555
9556         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
9557             this.wrap = Ext.DomHelper.insertHtml("beforeBegin", n.nextSibling.ui.getEl(), buf.join(''));
9558         }else{
9559             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(''));
9560         }
9561
9562         this.elNode = this.wrap.childNodes[0];
9563         this.ctNode = this.wrap.childNodes[1].firstChild.firstChild;
9564         var cs = this.elNode.firstChild.childNodes;
9565         this.indentNode = cs[0];
9566         this.ecNode = cs[1];
9567         this.iconNode = cs[2];
9568         this.anchor = cs[3];
9569         this.textNode = cs[3].firstChild;
9570     },
9571
9572     // private
9573     animExpand : function(cb){
9574         this.ctNode.style.display = "";
9575         Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb);
9576     }
9577 });
9578
9579 Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
9580     isTreeGridNodeUI: true,
9581     
9582     // private
9583     render : function(){
9584         if(!this.rendered){
9585             this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom;
9586             this.node.expanded = true;
9587         }
9588         
9589         if(Ext.isWebKit) {
9590             // weird table-layout: fixed issue in webkit
9591             var ct = this.ctNode;
9592             ct.style.tableLayout = null;
9593             (function() {
9594                 ct.style.tableLayout = 'fixed';
9595             }).defer(1);
9596         }
9597     },
9598
9599     destroy : function(){
9600         if(this.elNode){
9601             Ext.dd.Registry.unregister(this.elNode.id);
9602         }
9603         delete this.node;
9604     },
9605     
9606     collapse : Ext.emptyFn,
9607     expand : Ext.emptyFn
9608 });/**
9609  * @class Ext.tree.ColumnResizer
9610  * @extends Ext.util.Observable
9611  */
9612 Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, {
9613     /**
9614      * @cfg {Number} minWidth The minimum width the column can be dragged to.
9615      * Defaults to <tt>14</tt>.
9616      */
9617     minWidth: 14,
9618
9619     constructor: function(config){
9620         Ext.apply(this, config);
9621         Ext.tree.ColumnResizer.superclass.constructor.call(this);
9622     },
9623
9624     init : function(tree){
9625         this.tree = tree;
9626         tree.on('render', this.initEvents, this);
9627     },
9628
9629     initEvents : function(tree){
9630         tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this);
9631         this.tracker = new Ext.dd.DragTracker({
9632             onBeforeStart: this.onBeforeStart.createDelegate(this),
9633             onStart: this.onStart.createDelegate(this),
9634             onDrag: this.onDrag.createDelegate(this),
9635             onEnd: this.onEnd.createDelegate(this),
9636             tolerance: 3,
9637             autoStart: 300
9638         });
9639         this.tracker.initEl(tree.innerHd);
9640         tree.on('beforedestroy', this.tracker.destroy, this.tracker);
9641     },
9642
9643     handleHdMove : function(e, t){
9644         var hw = 5,
9645             x = e.getPageX(),
9646             hd = e.getTarget('.x-treegrid-hd', 3, true);
9647         
9648         if(hd){                                 
9649             var r = hd.getRegion(),
9650                 ss = hd.dom.style,
9651                 pn = hd.dom.parentNode;
9652             
9653             if(x - r.left <= hw && hd.dom !== pn.firstChild) {
9654                 var ps = hd.dom.previousSibling;
9655                 while(ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) {
9656                     ps = ps.previousSibling;
9657                 }
9658                 if(ps) {                    
9659                     this.activeHd = Ext.get(ps);
9660                                 ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
9661                 }
9662             } else if(r.right - x <= hw) {
9663                 var ns = hd.dom;
9664                 while(ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) {
9665                     ns = ns.previousSibling;
9666                 }
9667                 if(ns) {
9668                     this.activeHd = Ext.get(ns);
9669                                 ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';                    
9670                 }
9671             } else{
9672                 delete this.activeHd;
9673                 ss.cursor = '';
9674             }
9675         }
9676     },
9677
9678     onBeforeStart : function(e){
9679         this.dragHd = this.activeHd;
9680         return !!this.dragHd;
9681     },
9682
9683     onStart : function(e){
9684         this.tree.headersDisabled = true;
9685         this.proxy = this.tree.body.createChild({cls:'x-treegrid-resizer'});
9686         this.proxy.setHeight(this.tree.body.getHeight());
9687
9688         var x = this.tracker.getXY()[0];
9689
9690         this.hdX = this.dragHd.getX();
9691         this.hdIndex = this.tree.findHeaderIndex(this.dragHd);
9692
9693         this.proxy.setX(this.hdX);
9694         this.proxy.setWidth(x-this.hdX);
9695
9696         this.maxWidth = this.tree.outerCt.getWidth() - this.tree.innerBody.translatePoints(this.hdX).left;
9697     },
9698
9699     onDrag : function(e){
9700         var cursorX = this.tracker.getXY()[0];
9701         this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
9702     },
9703
9704     onEnd : function(e){
9705         var nw = this.proxy.getWidth(),
9706             tree = this.tree;
9707         
9708         this.proxy.remove();
9709         delete this.dragHd;
9710         
9711         tree.columns[this.hdIndex].width = nw;
9712         tree.updateColumnWidths();
9713         
9714         setTimeout(function(){
9715             tree.headersDisabled = false;
9716         }, 100);
9717     }
9718 });Ext.ns('Ext.ux.tree');
9719
9720 /**
9721  * @class Ext.ux.tree.TreeGridSorter
9722  * @extends Ext.tree.TreeSorter
9723  */
9724 Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
9725     /**
9726      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
9727      */
9728     sortClasses : ['sort-asc', 'sort-desc'],
9729     /**
9730      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
9731      */
9732     sortAscText : 'Sort Ascending',
9733     /**
9734      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
9735      */
9736     sortDescText : 'Sort Descending',
9737
9738     constructor : function(tree, config) {
9739         if(!Ext.isObject(config)) {
9740             config = {
9741                 property: tree.columns[0].dataIndex || 'text',
9742                 folderSort: true
9743             }
9744         }
9745
9746         Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments);
9747
9748         this.tree = tree;
9749         tree.on('headerclick', this.onHeaderClick, this);
9750         tree.ddAppendOnly = true;
9751
9752         me = this;
9753         this.defaultSortFn = function(n1, n2){
9754
9755             var dsc = me.dir && me.dir.toLowerCase() == 'desc';
9756             var p = me.property || 'text';
9757             var sortType = me.sortType;
9758             var fs = me.folderSort;
9759             var cs = me.caseSensitive === true;
9760             var leafAttr = me.leafAttr || 'leaf';
9761
9762             if(fs){
9763                 if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
9764                     return 1;
9765                 }
9766                 if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
9767                     return -1;
9768                 }
9769             }
9770                 var v1 = sortType ? sortType(n1.attributes[p]) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
9771                 var v2 = sortType ? sortType(n2.attributes[p]) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
9772                 if(v1 < v2){
9773                         return dsc ? +1 : -1;
9774                 }else if(v1 > v2){
9775                         return dsc ? -1 : +1;
9776             }else{
9777                 return 0;
9778             }
9779         };
9780
9781         tree.on('afterrender', this.onAfterTreeRender, this, {single: true});
9782         tree.on('headermenuclick', this.onHeaderMenuClick, this);
9783     },
9784
9785     onAfterTreeRender : function() {
9786         var hmenu = this.tree.hmenu;
9787         hmenu.insert(0,
9788             {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
9789             {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
9790         );
9791         this.updateSortIcon(0, 'asc');
9792     },
9793
9794     onHeaderMenuClick : function(c, id, index) {
9795         if(id === 'asc' || id === 'desc') {
9796             this.onHeaderClick(c, null, index);
9797             return false;
9798         }
9799     },
9800
9801     onHeaderClick : function(c, el, i) {
9802         if(c && !this.tree.headersDisabled){
9803             var me = this;
9804
9805             me.property = c.dataIndex;
9806             me.dir = c.dir = (c.dir === 'desc' ? 'asc' : 'desc');
9807             me.sortType = c.sortType;
9808             me.caseSensitive === Ext.isBoolean(c.caseSensitive) ? c.caseSensitive : this.caseSensitive;
9809             me.sortFn = c.sortFn || this.defaultSortFn;
9810
9811             this.tree.root.cascade(function(n) {
9812                 if(!n.isLeaf()) {
9813                     me.updateSort(me.tree, n);
9814                 }
9815             });
9816
9817             this.updateSortIcon(i, c.dir);
9818         }
9819     },
9820
9821     // private
9822     updateSortIcon : function(col, dir){
9823         var sc = this.sortClasses;
9824         var hds = this.tree.innerHd.select('td').removeClass(sc);
9825         hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]);
9826     }
9827 });/**
9828  * @class Ext.ux.tree.TreeGridLoader
9829  * @extends Ext.tree.TreeLoader
9830  */
9831 Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, {
9832     createNode : function(attr) {
9833         if (!attr.uiProvider) {
9834             attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
9835         }
9836         return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
9837     }
9838 });/**
9839  * @class Ext.ux.tree.TreeGrid
9840  * @extends Ext.tree.TreePanel
9841  * 
9842  * @xtype treegrid
9843  */
9844 Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
9845     rootVisible : false,
9846     useArrows : true,
9847     lines : false,
9848     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
9849     cls : 'x-treegrid',
9850
9851     columnResize : true,
9852     enableSort : true,
9853     reserveScrollOffset : true,
9854     enableHdMenu : true,
9855     
9856     columnsText : 'Columns',
9857
9858     initComponent : function() {
9859         if(!this.root) {
9860             this.root = new Ext.tree.AsyncTreeNode({text: 'Root'});
9861         }
9862         
9863         // initialize the loader
9864         var l = this.loader;
9865         if(!l){
9866             l = new Ext.ux.tree.TreeGridLoader({
9867                 dataUrl: this.dataUrl,
9868                 requestMethod: this.requestMethod,
9869                 store: this.store
9870             });
9871         }else if(Ext.isObject(l) && !l.load){
9872             l = new Ext.ux.tree.TreeGridLoader(l);
9873         }
9874         else if(l) {
9875             l.createNode = function(attr) {
9876                 if (!attr.uiProvider) {
9877                     attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
9878                 }
9879                 return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
9880             }
9881         }
9882         this.loader = l;
9883                             
9884         Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);                    
9885         
9886         this.initColumns();
9887         
9888         if(this.enableSort) {
9889             this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort);
9890         }
9891         
9892         if(this.columnResize){
9893             this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
9894             this.colResizer.init(this);
9895         }
9896         
9897         var c = this.columns;
9898         if(!this.internalTpl){                                
9899             this.internalTpl = new Ext.XTemplate(
9900                 '<div class="x-grid3-header">',
9901                     '<div class="x-treegrid-header-inner">',
9902                         '<div class="x-grid3-header-offset">',
9903                             '<table cellspacing="0" cellpadding="0" border="0"><colgroup><tpl for="columns"><col /></tpl></colgroup>',
9904                             '<thead><tr class="x-grid3-hd-row">',
9905                             '<tpl for="columns">',
9906                             '<td class="x-grid3-hd x-grid3-cell x-treegrid-hd" style="text-align: {align};" id="', this.id, '-xlhd-{#}">',
9907                                 '<div class="x-grid3-hd-inner x-treegrid-hd-inner" unselectable="on">',
9908                                      this.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
9909                                      '{header}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
9910                                  '</div>',
9911                             '</td></tpl>',
9912                             '</tr></thead>',
9913                         '</div></table>',
9914                     '</div></div>',
9915                 '</div>',
9916                 '<div class="x-treegrid-root-node">',
9917                     '<table class="x-treegrid-root-table" cellpadding="0" cellspacing="0" style="table-layout: fixed;"></table>',
9918                 '</div>'
9919             );
9920         }
9921         
9922         if(!this.colgroupTpl) {
9923             this.colgroupTpl = new Ext.XTemplate(
9924                 '<colgroup><tpl for="columns"><col style="width: {width}px"/></tpl></colgroup>'
9925             );
9926         }
9927     },
9928
9929     initColumns : function() {
9930         var cs = this.columns,
9931             len = cs.length, 
9932             columns = [],
9933             i, c;
9934
9935         for(i = 0; i < len; i++){
9936             c = cs[i];
9937             if(!c.isColumn) {
9938                 c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn';
9939                 c = Ext.create(c);
9940             }
9941             c.init(this);
9942             columns.push(c);
9943             
9944             if(this.enableSort !== false && c.sortable !== false) {
9945                 c.sortable = true;
9946                 this.enableSort = true;
9947             }
9948         }
9949
9950         this.columns = columns;
9951     },
9952
9953     onRender : function(){
9954         Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
9955
9956         this.el.addClass('x-treegrid');
9957         
9958         this.outerCt = this.body.createChild({
9959             cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')
9960         });
9961         
9962         this.internalTpl.overwrite(this.outerCt, {columns: this.columns});
9963         
9964         this.mainHd = Ext.get(this.outerCt.dom.firstChild);
9965         this.innerHd = Ext.get(this.mainHd.dom.firstChild);
9966         this.innerBody = Ext.get(this.outerCt.dom.lastChild);
9967         this.innerCt = Ext.get(this.innerBody.dom.firstChild);
9968         
9969         this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
9970         
9971         if(this.hideHeaders){
9972             this.header.dom.style.display = 'none';
9973         }
9974         else if(this.enableHdMenu !== false){
9975             this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'});
9976             if(this.enableColumnHide !== false){
9977                 this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'});
9978                 this.colMenu.on({
9979                     scope: this,
9980                     beforeshow: this.beforeColMenuShow,
9981                     itemclick: this.handleHdMenuClick
9982                 });
9983                 this.hmenu.add({
9984                     itemId:'columns',
9985                     hideOnClick: false,
9986                     text: this.columnsText,
9987                     menu: this.colMenu,
9988                     iconCls: 'x-cols-icon'
9989                 });
9990             }
9991             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
9992         }
9993     },
9994
9995     setRootNode : function(node){
9996         node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;        
9997         node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
9998         if(this.innerCt) {
9999             this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
10000         }
10001         return node;
10002     },
10003     
10004     initEvents : function() {
10005         Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
10006
10007         this.mon(this.innerBody, 'scroll', this.syncScroll, this);
10008         this.mon(this.innerHd, 'click', this.handleHdDown, this);
10009         this.mon(this.mainHd, {
10010             scope: this,
10011             mouseover: this.handleHdOver,
10012             mouseout: this.handleHdOut
10013         });
10014     },
10015     
10016     onResize : function(w, h) {
10017         Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
10018         
10019         var bd = this.innerBody.dom;
10020         var hd = this.innerHd.dom;
10021
10022         if(!bd){
10023             return;
10024         }
10025
10026         if(Ext.isNumber(h)){
10027             bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px';
10028         }
10029
10030         if(Ext.isNumber(w)){                        
10031             var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
10032             if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){
10033                 this.setScrollOffset(sw);
10034             }else{
10035                 var me = this;
10036                 setTimeout(function(){
10037                     me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0);
10038                 }, 10);
10039             }
10040         }
10041     },
10042
10043     updateColumnWidths : function() {
10044         var cols = this.columns,
10045             colCount = cols.length,
10046             groups = this.outerCt.query('colgroup'),
10047             groupCount = groups.length,
10048             c, g, i, j;
10049
10050         for(i = 0; i<colCount; i++) {
10051             c = cols[i];
10052             for(j = 0; j<groupCount; j++) {
10053                 g = groups[j];
10054                 g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
10055             }
10056         }
10057         
10058         for(i = 0, groups = this.innerHd.query('td'), len = groups.length; i<len; i++) {
10059             c = Ext.fly(groups[i]);
10060             if(cols[i] && cols[i].hidden) {
10061                 c.addClass('x-treegrid-hd-hidden');
10062             }
10063             else {
10064                 c.removeClass('x-treegrid-hd-hidden');
10065             }
10066         }
10067
10068         var tcw = this.getTotalColumnWidth();                        
10069         Ext.fly(this.innerHd.dom.firstChild).setWidth(tcw + (this.scrollOffset || 0));
10070         this.outerCt.select('table').setWidth(tcw);
10071         this.syncHeaderScroll();    
10072     },
10073                     
10074     getVisibleColumns : function() {
10075         var columns = [],
10076             cs = this.columns,
10077             len = cs.length,
10078             i;
10079             
10080         for(i = 0; i<len; i++) {
10081             if(!cs[i].hidden) {
10082                 columns.push(cs[i]);
10083             }
10084         }        
10085         return columns;
10086     },
10087
10088     getTotalColumnWidth : function() {
10089         var total = 0;
10090         for(var i = 0, cs = this.getVisibleColumns(), len = cs.length; i<len; i++) {
10091             total += cs[i].width;
10092         }
10093         return total;
10094     },
10095
10096     setScrollOffset : function(scrollOffset) {
10097         this.scrollOffset = scrollOffset;                        
10098         this.updateColumnWidths();
10099     },
10100
10101     // private
10102     handleHdDown : function(e, t){
10103         var hd = e.getTarget('.x-treegrid-hd');
10104
10105         if(hd && Ext.fly(t).hasClass('x-grid3-hd-btn')){
10106             var ms = this.hmenu.items,
10107                 cs = this.columns,
10108                 index = this.findHeaderIndex(hd),
10109                 c = cs[index],
10110                 sort = c.sortable;
10111                 
10112             e.stopEvent();
10113             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
10114             this.hdCtxIndex = index;
10115             
10116             this.fireEvent('headerbuttonclick', ms, c, hd, index);
10117             
10118             this.hmenu.on('hide', function(){
10119                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
10120             }, this, {single:true});
10121             
10122             this.hmenu.show(t, 'tl-bl?');
10123         }
10124         else if(hd) {
10125             var index = this.findHeaderIndex(hd);
10126             this.fireEvent('headerclick', this.columns[index], hd, index);
10127         }
10128     },
10129
10130     // private
10131     handleHdOver : function(e, t){                    
10132         var hd = e.getTarget('.x-treegrid-hd');                        
10133         if(hd && !this.headersDisabled){
10134             index = this.findHeaderIndex(hd);
10135             this.activeHdRef = t;
10136             this.activeHdIndex = index;
10137             var el = Ext.get(hd);
10138             this.activeHdRegion = el.getRegion();
10139             el.addClass('x-grid3-hd-over');
10140             this.activeHdBtn = el.child('.x-grid3-hd-btn');
10141             if(this.activeHdBtn){
10142                 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
10143             }
10144         }
10145     },
10146     
10147     // private
10148     handleHdOut : function(e, t){
10149         var hd = e.getTarget('.x-treegrid-hd');
10150         if(hd && (!Ext.isIE || !e.within(hd, true))){
10151             this.activeHdRef = null;
10152             Ext.fly(hd).removeClass('x-grid3-hd-over');
10153             hd.style.cursor = '';
10154         }
10155     },
10156                     
10157     findHeaderIndex : function(hd){
10158         hd = hd.dom || hd;
10159         var cs = hd.parentNode.childNodes;
10160         for(var i = 0, c; c = cs[i]; i++){
10161             if(c == hd){
10162                 return i;
10163             }
10164         }
10165         return -1;
10166     },
10167     
10168     // private
10169     beforeColMenuShow : function(){
10170         var cols = this.columns,  
10171             colCount = cols.length,
10172             i, c;                        
10173         this.colMenu.removeAll();                    
10174         for(i = 1; i < colCount; i++){
10175             c = cols[i];
10176             if(c.hideable !== false){
10177                 this.colMenu.add(new Ext.menu.CheckItem({
10178                     itemId: 'col-' + i,
10179                     text: c.header,
10180                     checked: !c.hidden,
10181                     hideOnClick:false,
10182                     disabled: c.hideable === false
10183                 }));
10184             }
10185         }
10186     },
10187                     
10188     // private
10189     handleHdMenuClick : function(item){
10190         var index = this.hdCtxIndex,
10191             id = item.getItemId();
10192         
10193         if(this.fireEvent('headermenuclick', this.columns[index], id, index) !== false) {
10194             index = id.substr(4);
10195             if(index > 0 && this.columns[index]) {
10196                 this.setColumnVisible(index, !item.checked);
10197             }     
10198         }
10199         
10200         return true;
10201     },
10202     
10203     setColumnVisible : function(index, visible) {
10204         this.columns[index].hidden = !visible;        
10205         this.updateColumnWidths();
10206     },
10207
10208     /**
10209      * Scrolls the grid to the top
10210      */
10211     scrollToTop : function(){
10212         this.innerBody.dom.scrollTop = 0;
10213         this.innerBody.dom.scrollLeft = 0;
10214     },
10215
10216     // private
10217     syncScroll : function(){
10218         this.syncHeaderScroll();
10219         var mb = this.innerBody.dom;
10220         this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
10221     },
10222
10223     // private
10224     syncHeaderScroll : function(){
10225         var mb = this.innerBody.dom;
10226         this.innerHd.dom.scrollLeft = mb.scrollLeft;
10227         this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
10228     },
10229     
10230     registerNode : function(n) {
10231         Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
10232         if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
10233             n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
10234         }
10235     }
10236 });
10237
10238 Ext.reg('treegrid', Ext.ux.tree.TreeGrid);