Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / examples / ux / ux-all-debug.js
1 /*!
2  * Ext JS Library 3.0.3
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.tree');\r
350 \r
351 /**\r
352  * @class Ext.ux.tree.ColumnTree\r
353  * @extends Ext.tree.TreePanel\r
354  * \r
355  * @xtype columntree\r
356  */\r
357 Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {\r
358     lines : false,\r
359     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell\r
360     cls : 'x-column-tree',\r
361 \r
362     onRender : function(){\r
363         Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);\r
364         this.headers = this.header.createChild({cls:'x-tree-headers'});\r
365 \r
366         var cols = this.columns, c;\r
367         var totalWidth = 0;\r
368         var scrollOffset = 19; // similar to Ext.grid.GridView default\r
369 \r
370         for(var i = 0, len = cols.length; i < len; i++){\r
371              c = cols[i];\r
372              totalWidth += c.width;\r
373              this.headers.createChild({\r
374                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),\r
375                  cn: {\r
376                      cls:'x-tree-hd-text',\r
377                      html: c.header\r
378                  },\r
379                  style:'width:'+(c.width-this.borderWidth)+'px;'\r
380              });\r
381         }\r
382         this.headers.createChild({cls:'x-clear'});\r
383         // prevent floats from wrapping when clipped\r
384         this.headers.setWidth(totalWidth+scrollOffset);\r
385         this.innerCt.setWidth(totalWidth);\r
386     }\r
387 });\r
388 \r
389 Ext.reg('columntree', Ext.ux.tree.ColumnTree);\r
390 \r
391 //backwards compat\r
392 Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree;\r
393 \r
394 \r
395 /**\r
396  * @class Ext.ux.tree.ColumnNodeUI\r
397  * @extends Ext.tree.TreeNodeUI\r
398  */\r
399 Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {\r
400     focus: Ext.emptyFn, // prevent odd scrolling behavior\r
401 \r
402     renderElements : function(n, a, targetNode, bulkRender){\r
403         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';\r
404 \r
405         var t = n.getOwnerTree();\r
406         var cols = t.columns;\r
407         var bw = t.borderWidth;\r
408         var c = cols[0];\r
409 \r
410         var buf = [\r
411              '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',\r
412                 '<div class="x-tree-col" style="width:',c.width-bw,'px;">',\r
413                     '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",\r
414                     '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',\r
415                     '<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
416                     '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',\r
417                     a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',\r
418                     '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",\r
419                 "</div>"];\r
420          for(var i = 1, len = cols.length; i < len; i++){\r
421              c = cols[i];\r
422 \r
423              buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',\r
424                         '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",\r
425                       "</div>");\r
426          }\r
427          buf.push(\r
428             '<div class="x-clear"></div></div>',\r
429             '<ul class="x-tree-node-ct" style="display:none;"></ul>',\r
430             "</li>");\r
431 \r
432         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){\r
433             this.wrap = Ext.DomHelper.insertHtml("beforeBegin",\r
434                                 n.nextSibling.ui.getEl(), buf.join(""));\r
435         }else{\r
436             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));\r
437         }\r
438 \r
439         this.elNode = this.wrap.childNodes[0];\r
440         this.ctNode = this.wrap.childNodes[1];\r
441         var cs = this.elNode.firstChild.childNodes;\r
442         this.indentNode = cs[0];\r
443         this.ecNode = cs[1];\r
444         this.iconNode = cs[2];\r
445         this.anchor = cs[3];\r
446         this.textNode = cs[3].firstChild;\r
447     }\r
448 });\r
449 \r
450 //backwards compat\r
451 Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI;\r
452 /**\r
453  * @class Ext.DataView.LabelEditor\r
454  * @extends Ext.Editor\r
455  * \r
456  */\r
457 Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, {\r
458     alignment: "tl-tl",\r
459     hideEl : false,\r
460     cls: "x-small-editor",\r
461     shim: false,\r
462     completeOnEnter: true,\r
463     cancelOnEsc: true,\r
464     labelSelector: 'span.x-editable',\r
465     \r
466     constructor: function(cfg, field){\r
467         Ext.DataView.LabelEditor.superclass.constructor.call(this,\r
468             field || new Ext.form.TextField({\r
469                 allowBlank: false,\r
470                 growMin:90,\r
471                 growMax:240,\r
472                 grow:true,\r
473                 selectOnFocus:true\r
474             }), cfg\r
475         );\r
476     },\r
477     \r
478     init : function(view){\r
479         this.view = view;\r
480         view.on('render', this.initEditor, this);\r
481         this.on('complete', this.onSave, this);\r
482     },\r
483 \r
484     initEditor : function(){\r
485         this.view.on({\r
486             scope: this,\r
487             containerclick: this.doBlur,\r
488             click: this.doBlur\r
489         });\r
490         this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});\r
491     },\r
492     \r
493     doBlur: function(){\r
494         if(this.editing){\r
495             this.field.blur();\r
496         }\r
497     },\r
498 \r
499     onMouseDown : function(e, target){\r
500         if(!e.ctrlKey && !e.shiftKey){\r
501             var item = this.view.findItemFromChild(target);\r
502             e.stopEvent();\r
503             var record = this.view.store.getAt(this.view.indexOf(item));\r
504             this.startEdit(target, record.data[this.dataIndex]);\r
505             this.activeRecord = record;\r
506         }else{\r
507             e.preventDefault();\r
508         }\r
509     },\r
510 \r
511     onSave : function(ed, value){\r
512         this.activeRecord.set(this.dataIndex, value);\r
513     }\r
514 });\r
515 \r
516 \r
517 Ext.DataView.DragSelector = function(cfg){\r
518     cfg = cfg || {};\r
519     var view, proxy, tracker;\r
520     var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);\r
521     var dragSafe = cfg.dragSafe === true;\r
522 \r
523     this.init = function(dataView){\r
524         view = dataView;\r
525         view.on('render', onRender);\r
526     };\r
527 \r
528     function fillRegions(){\r
529         rs = [];\r
530         view.all.each(function(el){\r
531             rs[rs.length] = el.getRegion();\r
532         });\r
533         bodyRegion = view.el.getRegion();\r
534     }\r
535 \r
536     function cancelClick(){\r
537         return false;\r
538     }\r
539 \r
540     function onBeforeStart(e){\r
541         return !dragSafe || e.target == view.el.dom;\r
542     }\r
543 \r
544     function onStart(e){\r
545         view.on('containerclick', cancelClick, view, {single:true});\r
546         if(!proxy){\r
547             proxy = view.el.createChild({cls:'x-view-selector'});\r
548         }else{\r
549             if(proxy.dom.parentNode !== view.el.dom){\r
550                 view.el.dom.appendChild(proxy.dom);\r
551             }\r
552             proxy.setDisplayed('block');\r
553         }\r
554         fillRegions();\r
555         view.clearSelections();\r
556     }\r
557 \r
558     function onDrag(e){\r
559         var startXY = tracker.startXY;\r
560         var xy = tracker.getXY();\r
561 \r
562         var x = Math.min(startXY[0], xy[0]);\r
563         var y = Math.min(startXY[1], xy[1]);\r
564         var w = Math.abs(startXY[0] - xy[0]);\r
565         var h = Math.abs(startXY[1] - xy[1]);\r
566 \r
567         dragRegion.left = x;\r
568         dragRegion.top = y;\r
569         dragRegion.right = x+w;\r
570         dragRegion.bottom = y+h;\r
571 \r
572         dragRegion.constrainTo(bodyRegion);\r
573         proxy.setRegion(dragRegion);\r
574 \r
575         for(var i = 0, len = rs.length; i < len; i++){\r
576             var r = rs[i], sel = dragRegion.intersect(r);\r
577             if(sel && !r.selected){\r
578                 r.selected = true;\r
579                 view.select(i, true);\r
580             }else if(!sel && r.selected){\r
581                 r.selected = false;\r
582                 view.deselect(i);\r
583             }\r
584         }\r
585     }\r
586 \r
587     function onEnd(e){\r
588         if (!Ext.isIE) {\r
589             view.un('containerclick', cancelClick, view);    \r
590         }        \r
591         if(proxy){\r
592             proxy.setDisplayed(false);\r
593         }\r
594     }\r
595 \r
596     function onRender(view){\r
597         tracker = new Ext.dd.DragTracker({\r
598             onBeforeStart: onBeforeStart,\r
599             onStart: onStart,\r
600             onDrag: onDrag,\r
601             onEnd: onEnd\r
602         });\r
603         tracker.initEl(view.el);\r
604     }\r
605 };Ext.ns('Ext.ux.form');
606
607 /**
608  * @class Ext.ux.form.FileUploadField
609  * @extends Ext.form.TextField
610  * Creates a file upload field.
611  * @xtype fileuploadfield
612  */
613 Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
614     /**
615      * @cfg {String} buttonText The button text to display on the upload button (defaults to
616      * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
617      * value will be used instead if available.
618      */
619     buttonText: 'Browse...',
620     /**
621      * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
622      * text field (defaults to false).  If true, all inherited TextField members will still be available.
623      */
624     buttonOnly: false,
625     /**
626      * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
627      * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
628      */
629     buttonOffset: 3,
630     /**
631      * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
632      */
633
634     // private
635     readOnly: true,
636
637     /**
638      * @hide
639      * @method autoSize
640      */
641     autoSize: Ext.emptyFn,
642
643     // private
644     initComponent: function(){
645         Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
646
647         this.addEvents(
648             /**
649              * @event fileselected
650              * Fires when the underlying file input field's value has changed from the user
651              * selecting a new file from the system file selection dialog.
652              * @param {Ext.ux.form.FileUploadField} this
653              * @param {String} value The file value returned by the underlying file input field
654              */
655             'fileselected'
656         );
657     },
658
659     // private
660     onRender : function(ct, position){
661         Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);
662
663         this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
664         this.el.addClass('x-form-file-text');
665         this.el.dom.removeAttribute('name');
666         this.createFileInput();
667
668         var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
669             text: this.buttonText
670         });
671         this.button = new Ext.Button(Ext.apply(btnCfg, {
672             renderTo: this.wrap,
673             cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
674         }));
675
676         if(this.buttonOnly){
677             this.el.hide();
678             this.wrap.setWidth(this.button.getEl().getWidth());
679         }
680
681         this.bindListeners();
682         this.resizeEl = this.positionEl = this.wrap;
683     },
684     
685     bindListeners: function(){
686         this.fileInput.on({
687             scope: this,
688             mouseenter: function() {
689                 this.button.addClass(['x-btn-over','x-btn-focus'])
690             },
691             mouseleave: function(){
692                 this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
693             },
694             mousedown: function(){
695                 this.button.addClass('x-btn-click')
696             },
697             mouseup: function(){
698                 this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
699             },
700             change: function(){
701                 var v = this.fileInput.dom.value;
702                 this.setValue(v);
703                 this.fireEvent('fileselected', this, v);    
704             }
705         }); 
706     },
707     
708     createFileInput : function() {
709         this.fileInput = this.wrap.createChild({
710             id: this.getFileInputId(),
711             name: this.name||this.getId(),
712             cls: 'x-form-file',
713             tag: 'input',
714             type: 'file',
715             size: 1
716         });
717     },
718     
719     reset : function(){
720         this.fileInput.remove();
721         this.createFileInput();
722         this.bindListeners();
723         Ext.ux.form.FileUploadField.superclass.reset.call(this);
724     },
725
726     // private
727     getFileInputId: function(){
728         return this.id + '-file';
729     },
730
731     // private
732     onResize : function(w, h){
733         Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
734
735         this.wrap.setWidth(w);
736
737         if(!this.buttonOnly){
738             var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
739             this.el.setWidth(w);
740         }
741     },
742
743     // private
744     onDestroy: function(){
745         Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
746         Ext.destroy(this.fileInput, this.button, this.wrap);
747     },
748     
749     onDisable: function(){
750         Ext.ux.form.FileUploadField.superclass.onDisable.call(this);
751         this.doDisable(true);
752     },
753     
754     onEnable: function(){
755         Ext.ux.form.FileUploadField.superclass.onEnable.call(this);
756         this.doDisable(false);
757
758     },
759     
760     // private
761     doDisable: function(disabled){
762         this.fileInput.dom.disabled = disabled;
763         this.button.setDisabled(disabled);
764     },
765
766
767     // private
768     preFocus : Ext.emptyFn,
769
770     // private
771     alignErrorIcon : function(){
772         this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
773     }
774
775 });
776
777 Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
778
779 // backwards compat
780 Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
781 /**\r
782  * @class Ext.ux.GMapPanel\r
783  * @extends Ext.Panel\r
784  * @author Shea Frederick\r
785  */\r
786 Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {\r
787     initComponent : function(){\r
788         \r
789         var defConfig = {\r
790             plain: true,\r
791             zoomLevel: 3,\r
792             yaw: 180,\r
793             pitch: 0,\r
794             zoom: 0,\r
795             gmapType: 'map',\r
796             border: false\r
797         };\r
798         \r
799         Ext.applyIf(this,defConfig);\r
800         \r
801         Ext.ux.GMapPanel.superclass.initComponent.call(this);        \r
802 \r
803     },\r
804     afterRender : function(){\r
805         \r
806         var wh = this.ownerCt.getSize();\r
807         Ext.applyIf(this, wh);\r
808         \r
809         Ext.ux.GMapPanel.superclass.afterRender.call(this);    \r
810         \r
811         if (this.gmapType === 'map'){\r
812             this.gmap = new GMap2(this.body.dom);\r
813         }\r
814         \r
815         if (this.gmapType === 'panorama'){\r
816             this.gmap = new GStreetviewPanorama(this.body.dom);\r
817         }\r
818         \r
819         if (typeof this.addControl == 'object' && this.gmapType === 'map') {\r
820             this.gmap.addControl(this.addControl);\r
821         }\r
822         \r
823         if (typeof this.setCenter === 'object') {\r
824             if (typeof this.setCenter.geoCodeAddr === 'string'){\r
825                 this.geoCodeLookup(this.setCenter.geoCodeAddr);\r
826             }else{\r
827                 if (this.gmapType === 'map'){\r
828                     var point = new GLatLng(this.setCenter.lat,this.setCenter.lng);\r
829                     this.gmap.setCenter(point, this.zoomLevel);    \r
830                 }\r
831                 if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
832                     this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);\r
833                 }\r
834             }\r
835             if (this.gmapType === 'panorama'){\r
836                 this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});\r
837             }\r
838         }\r
839 \r
840         GEvent.bind(this.gmap, 'load', this, function(){\r
841             this.onMapReady();\r
842         });\r
843 \r
844     },\r
845     onMapReady : function(){\r
846         this.addMarkers(this.markers);\r
847         this.addMapControls();\r
848         this.addOptions();  \r
849     },\r
850     onResize : function(w, h){\r
851 \r
852         if (typeof this.getMap() == 'object') {\r
853             this.gmap.checkResize();\r
854         }\r
855         \r
856         Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);\r
857 \r
858     },\r
859     setSize : function(width, height, animate){\r
860         \r
861         if (typeof this.getMap() == 'object') {\r
862             this.gmap.checkResize();\r
863         }\r
864         \r
865         Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);\r
866         \r
867     },\r
868     getMap : function(){\r
869         \r
870         return this.gmap;\r
871         \r
872     },\r
873     getCenter : function(){\r
874         \r
875         return this.getMap().getCenter();\r
876         \r
877     },\r
878     getCenterLatLng : function(){\r
879         \r
880         var ll = this.getCenter();\r
881         return {lat: ll.lat(), lng: ll.lng()};\r
882         \r
883     },\r
884     addMarkers : function(markers) {\r
885         \r
886         if (Ext.isArray(markers)){\r
887             for (var i = 0; i < markers.length; i++) {\r
888                 var mkr_point = new GLatLng(markers[i].lat,markers[i].lng);\r
889                 this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners);\r
890             }\r
891         }\r
892         \r
893     },\r
894     addMarker : function(point, marker, clear, center, listeners){\r
895         \r
896         Ext.applyIf(marker,G_DEFAULT_ICON);\r
897 \r
898         if (clear === true){\r
899             this.getMap().clearOverlays();\r
900         }\r
901         if (center === true) {\r
902             this.getMap().setCenter(point, this.zoomLevel);\r
903         }\r
904 \r
905         var mark = new GMarker(point,marker);\r
906         if (typeof listeners === 'object'){\r
907             for (evt in listeners) {\r
908                 GEvent.bind(mark, evt, this, listeners[evt]);\r
909             }\r
910         }\r
911         this.getMap().addOverlay(mark);\r
912 \r
913     },\r
914     addMapControls : function(){\r
915         \r
916         if (this.gmapType === 'map') {\r
917             if (Ext.isArray(this.mapControls)) {\r
918                 for(i=0;i<this.mapControls.length;i++){\r
919                     this.addMapControl(this.mapControls[i]);\r
920                 }\r
921             }else if(typeof this.mapControls === 'string'){\r
922                 this.addMapControl(this.mapControls);\r
923             }else if(typeof this.mapControls === 'object'){\r
924                 this.getMap().addControl(this.mapControls);\r
925             }\r
926         }\r
927         \r
928     },\r
929     addMapControl : function(mc){\r
930         \r
931         var mcf = window[mc];\r
932         if (typeof mcf === 'function') {\r
933             this.getMap().addControl(new mcf());\r
934         }    \r
935         \r
936     },\r
937     addOptions : function(){\r
938         \r
939         if (Ext.isArray(this.mapConfOpts)) {\r
940             var mc;\r
941             for(i=0;i<this.mapConfOpts.length;i++){\r
942                 this.addOption(this.mapConfOpts[i]);\r
943             }\r
944         }else if(typeof this.mapConfOpts === 'string'){\r
945             this.addOption(this.mapConfOpts);\r
946         }        \r
947         \r
948     },\r
949     addOption : function(mc){\r
950         \r
951         var mcf = this.getMap()[mc];\r
952         if (typeof mcf === 'function') {\r
953             this.getMap()[mc]();\r
954         }    \r
955         \r
956     },\r
957     geoCodeLookup : function(addr) {\r
958         \r
959         this.geocoder = new GClientGeocoder();\r
960         this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));\r
961         \r
962     },\r
963     addAddressToMap : function(response) {\r
964         \r
965         if (!response || response.Status.code != 200) {\r
966             Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');\r
967         }else{\r
968             place = response.Placemark[0];\r
969             addressinfo = place.AddressDetails;\r
970             accuracy = addressinfo.Accuracy;\r
971             if (accuracy === 0) {\r
972                 Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');\r
973             }else{\r
974                 if (accuracy < 7) {\r
975                     Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');\r
976                 }else{\r
977                     point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);\r
978                     if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
979                         this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners);\r
980                     }\r
981                 }\r
982             }\r
983         }\r
984         \r
985     }\r
986  \r
987 });\r
988 \r
989 Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.namespace('Ext.ux.grid');\r
990 \r
991 /**\r
992  * @class Ext.ux.grid.GridFilters\r
993  * @extends Ext.util.Observable\r
994  * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that\r
995  * allow for a slightly more robust representation of filtering than what is\r
996  * provided by the default store.</p>\r
997  * <p>Filtering is adjusted by the user using the grid's column header menu\r
998  * (this menu can be disabled through configuration). Through this menu users\r
999  * can configure, enable, and disable filters for each column.</p>\r
1000  * <p><b><u>Features:</u></b></p>\r
1001  * <div class="mdetail-params"><ul>\r
1002  * <li><b>Filtering implementations</b> :\r
1003  * <div class="sub-desc">\r
1004  * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can\r
1005  * be backed by a Ext.data.Store), and Boolean. Additional custom filter types\r
1006  * and menus are easily created by extending Ext.ux.grid.filter.Filter.\r
1007  * </div></li>\r
1008  * <li><b>Graphical indicators</b> :\r
1009  * <div class="sub-desc">\r
1010  * Columns that are filtered have {@link #filterCls a configurable css class}\r
1011  * applied to the column headers.\r
1012  * </div></li>\r
1013  * <li><b>Paging</b> :\r
1014  * <div class="sub-desc">\r
1015  * If specified as a plugin to the grid's configured PagingToolbar, the current page\r
1016  * will be reset to page 1 whenever you update the filters.\r
1017  * </div></li>\r
1018  * <li><b>Automatic Reconfiguration</b> :\r
1019  * <div class="sub-desc">\r
1020  * Filters automatically reconfigure when the grid 'reconfigure' event fires.\r
1021  * </div></li>\r
1022  * <li><b>Stateful</b> :\r
1023  * Filter information will be persisted across page loads by specifying a\r
1024  * <code>stateId</code> in the Grid configuration.\r
1025  * <div class="sub-desc">\r
1026  * The filter collection binds to the\r
1027  * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code>\r
1028  * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code>\r
1029  * events in order to be stateful. \r
1030  * </div></li>\r
1031  * <li><b>Grid Changes</b> :\r
1032  * <div class="sub-desc"><ul>\r
1033  * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to\r
1034  * this plugin.</li>\r
1035  * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is\r
1036  * fired upon onStateChange completion.</li>\r
1037  * </ul></div></li>\r
1038  * <li><b>Server side code examples</b> :\r
1039  * <div class="sub-desc"><ul>\r
1040  * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li>\r
1041  * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li>\r
1042  * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li>\r
1043  * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li>\r
1044  * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li>\r
1045  * </ul></div></li>\r
1046  * </ul></div>\r
1047  * <p><b><u>Example usage:</u></b></p>\r
1048  * <pre><code>    \r
1049 var store = new Ext.data.GroupingStore({\r
1050     ...\r
1051 });\r
1052  \r
1053 var filters = new Ext.ux.grid.GridFilters({\r
1054     autoReload: false, //don&#39;t reload automatically\r
1055     local: true, //only filter locally\r
1056     // filters may be configured through the plugin,\r
1057     // or in the column definition within the column model configuration\r
1058     filters: [{\r
1059         type: 'numeric',\r
1060         dataIndex: 'id'\r
1061     }, {\r
1062         type: 'string',\r
1063         dataIndex: 'name'\r
1064     }, {\r
1065         type: 'numeric',\r
1066         dataIndex: 'price'\r
1067     }, {\r
1068         type: 'date',\r
1069         dataIndex: 'dateAdded'\r
1070     }, {\r
1071         type: 'list',\r
1072         dataIndex: 'size',\r
1073         options: ['extra small', 'small', 'medium', 'large', 'extra large'],\r
1074         phpMode: true\r
1075     }, {\r
1076         type: 'boolean',\r
1077         dataIndex: 'visible'\r
1078     }]\r
1079 });\r
1080 var cm = new Ext.grid.ColumnModel([{\r
1081     ...\r
1082 }]);\r
1083  \r
1084 var grid = new Ext.grid.GridPanel({\r
1085      ds: store,\r
1086      cm: cm,\r
1087      view: new Ext.grid.GroupingView(),\r
1088      plugins: [filters],\r
1089      height: 400,\r
1090      width: 700,\r
1091      bbar: new Ext.PagingToolbar({\r
1092          store: store,\r
1093          pageSize: 15,\r
1094          plugins: [filters] //reset page to page 1 if filters change\r
1095      })\r
1096  });\r
1097 \r
1098 store.load({params: {start: 0, limit: 15}});\r
1099 \r
1100 // a filters property is added to the grid\r
1101 grid.filters\r
1102  * </code></pre>\r
1103  */\r
1104 Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {\r
1105     /**\r
1106      * @cfg {Boolean} autoReload\r
1107      * Defaults to true, reloading the datasource when a filter change happens.\r
1108      * Set this to false to prevent the datastore from being reloaded if there\r
1109      * are changes to the filters.  See <code>{@link updateBuffer}</code>.\r
1110      */\r
1111     autoReload : true,\r
1112     /**\r
1113      * @cfg {Boolean} encode\r
1114      * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to\r
1115      * encode the filter query parameter sent with a remote request.\r
1116      * Defaults to false.\r
1117      */\r
1118     /**\r
1119      * @cfg {Array} filters\r
1120      * An Array of filters config objects. Refer to each filter type class for\r
1121      * configuration details specific to each filter type. Filters for Strings,\r
1122      * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters\r
1123      * available.\r
1124      */\r
1125     /**\r
1126      * @cfg {String} filterCls\r
1127      * The css class to be applied to column headers with active filters.\r
1128      * Defaults to <tt>'ux-filterd-column'</tt>.\r
1129      */\r
1130     filterCls : 'ux-filtered-column',\r
1131     /**\r
1132      * @cfg {Boolean} local\r
1133      * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)\r
1134      * instead of the default (<tt>false</tt>) server side filtering.\r
1135      */\r
1136     local : false,\r
1137     /**\r
1138      * @cfg {String} menuFilterText\r
1139      * defaults to <tt>'Filters'</tt>.\r
1140      */\r
1141     menuFilterText : 'Filters',\r
1142     /**\r
1143      * @cfg {String} paramPrefix\r
1144      * The url parameter prefix for the filters.\r
1145      * Defaults to <tt>'filter'</tt>.\r
1146      */\r
1147     paramPrefix : 'filter',\r
1148     /**\r
1149      * @cfg {Boolean} showMenu\r
1150      * Defaults to true, including a filter submenu in the default header menu.\r
1151      */\r
1152     showMenu : true,\r
1153     /**\r
1154      * @cfg {String} stateId\r
1155      * Name of the value to be used to store state information.\r
1156      */\r
1157     stateId : undefined,\r
1158     /**\r
1159      * @cfg {Integer} updateBuffer\r
1160      * Number of milliseconds to defer store updates since the last filter change.\r
1161      */\r
1162     updateBuffer : 500,\r
1163 \r
1164     /** @private */\r
1165     constructor : function (config) {           \r
1166         this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);\r
1167         this.filters = new Ext.util.MixedCollection();\r
1168         this.filters.getKey = function (o) {\r
1169             return o ? o.dataIndex : null;\r
1170         };\r
1171         this.addFilters(config.filters);\r
1172         delete config.filters;\r
1173         Ext.apply(this, config);\r
1174     },\r
1175 \r
1176     /** @private */\r
1177     init : function (grid) {\r
1178         if (grid instanceof Ext.grid.GridPanel) {\r
1179             this.grid = grid;\r
1180 \r
1181             this.bindStore(this.grid.getStore(), true);\r
1182           \r
1183             this.grid.filters = this;\r
1184              \r
1185             this.grid.addEvents({'filterupdate': true});\r
1186               \r
1187             grid.on({\r
1188                 scope: this,\r
1189                 beforestaterestore: this.applyState,\r
1190                 beforestatesave: this.saveState,\r
1191                 beforedestroy: this.destroy,\r
1192                 reconfigure: this.onReconfigure\r
1193             });\r
1194             \r
1195             if (grid.rendered){\r
1196                 this.onRender();\r
1197             } else {\r
1198                 grid.on({\r
1199                     scope: this,\r
1200                     single: true,\r
1201                     render: this.onRender\r
1202                 });\r
1203             }\r
1204                       \r
1205         } else if (grid instanceof Ext.PagingToolbar) {\r
1206             this.toolbar = grid;\r
1207         }\r
1208     },\r
1209         \r
1210     /**\r
1211      * @private\r
1212      * Handler for the grid's beforestaterestore event (fires before the state of the\r
1213      * grid is restored).\r
1214      * @param {Object} grid The grid object\r
1215      * @param {Object} state The hash of state values returned from the StateProvider.\r
1216      */   \r
1217     applyState : function (grid, state) {\r
1218         var key, filter;\r
1219         this.applyingState = true;\r
1220         this.clearFilters();\r
1221         if (state.filters) {\r
1222             for (key in state.filters) {\r
1223                 filter = this.filters.get(key);\r
1224                 if (filter) {\r
1225                     filter.setValue(state.filters[key]);\r
1226                     filter.setActive(true);\r
1227                 }\r
1228             }\r
1229         }\r
1230         this.deferredUpdate.cancel();\r
1231         if (this.local) {\r
1232             this.reload();\r
1233         }\r
1234         delete this.applyingState;\r
1235     },\r
1236     \r
1237     /**\r
1238      * Saves the state of all active filters\r
1239      * @param {Object} grid\r
1240      * @param {Object} state\r
1241      * @return {Boolean}\r
1242      */\r
1243     saveState : function (grid, state) {\r
1244         var filters = {};\r
1245         this.filters.each(function (filter) {\r
1246             if (filter.active) {\r
1247                 filters[filter.dataIndex] = filter.getValue();\r
1248             }\r
1249         });\r
1250         return (state.filters = filters);\r
1251     },\r
1252     \r
1253     /**\r
1254      * @private\r
1255      * Handler called when the grid is rendered\r
1256      */    \r
1257     onRender : function () {\r
1258         this.grid.getView().on('refresh', this.onRefresh, this);\r
1259         this.createMenu();\r
1260     },\r
1261 \r
1262     /**\r
1263      * @private\r
1264      * Handler called by the grid 'beforedestroy' event\r
1265      */    \r
1266     destroy : function () {\r
1267         this.removeAll();\r
1268         this.purgeListeners();\r
1269 \r
1270         if(this.filterMenu){\r
1271             Ext.menu.MenuMgr.unregister(this.filterMenu);\r
1272             this.filterMenu.destroy();\r
1273              this.filterMenu = this.menu.menu = null;            \r
1274         }\r
1275     },\r
1276 \r
1277     /**\r
1278      * Remove all filters, permanently destroying them.\r
1279      */    \r
1280     removeAll : function () {\r
1281         if(this.filters){\r
1282             Ext.destroy.apply(Ext, this.filters.items);\r
1283             // remove all items from the collection \r
1284             this.filters.clear();\r
1285         }\r
1286     },\r
1287 \r
1288 \r
1289     /**\r
1290      * Changes the data store bound to this view and refreshes it.\r
1291      * @param {Store} store The store to bind to this view\r
1292      */\r
1293     bindStore : function(store, initial){\r
1294         if(!initial && this.store){\r
1295             if (this.local) {\r
1296                 store.un('load', this.onLoad, this);\r
1297             } else {\r
1298                 store.un('beforeload', this.onBeforeLoad, this);\r
1299             }\r
1300         }\r
1301         if(store){\r
1302             if (this.local) {\r
1303                 store.on('load', this.onLoad, this);\r
1304             } else {\r
1305                 store.on('beforeload', this.onBeforeLoad, this);\r
1306             }\r
1307         }\r
1308         this.store = store;\r
1309     },\r
1310 \r
1311     /**\r
1312      * @private\r
1313      * Handler called when the grid reconfigure event fires\r
1314      */    \r
1315     onReconfigure : function () {\r
1316         this.bindStore(this.grid.getStore());\r
1317         this.store.clearFilter();\r
1318         this.removeAll();\r
1319         this.addFilters(this.grid.getColumnModel());\r
1320         this.updateColumnHeadings();\r
1321     },\r
1322 \r
1323     createMenu : function () {\r
1324         var view = this.grid.getView(),\r
1325             hmenu = view.hmenu;\r
1326 \r
1327         if (this.showMenu && hmenu) {\r
1328             \r
1329             this.sep  = hmenu.addSeparator();\r
1330             this.filterMenu = new Ext.menu.Menu({\r
1331                 id: this.grid.id + '-filters-menu'\r
1332             }); \r
1333             this.menu = hmenu.add({\r
1334                 checked: false,\r
1335                 itemId: 'filters',\r
1336                 text: this.menuFilterText,\r
1337                 menu: this.filterMenu\r
1338             });\r
1339 \r
1340             this.menu.on({\r
1341                 scope: this,\r
1342                 checkchange: this.onCheckChange,\r
1343                 beforecheckchange: this.onBeforeCheck\r
1344             });\r
1345             hmenu.on('beforeshow', this.onMenu, this);\r
1346         }\r
1347         this.updateColumnHeadings();\r
1348     },\r
1349 \r
1350     /**\r
1351      * @private\r
1352      * Get the filter menu from the filters MixedCollection based on the clicked header\r
1353      */\r
1354     getMenuFilter : function () {\r
1355         var view = this.grid.getView();\r
1356         if (!view || view.hdCtxIndex === undefined) {\r
1357             return null;\r
1358         }\r
1359         return this.filters.get(\r
1360             view.cm.config[view.hdCtxIndex].dataIndex\r
1361         );\r
1362     },\r
1363 \r
1364     /**\r
1365      * @private\r
1366      * Handler called by the grid's hmenu beforeshow event\r
1367      */    \r
1368     onMenu : function (filterMenu) {\r
1369         var filter = this.getMenuFilter();\r
1370 \r
1371         if (filter) {\r
1372 /*            \r
1373 TODO: lazy rendering\r
1374             if (!filter.menu) {\r
1375                 filter.menu = filter.createMenu();\r
1376             }\r
1377 */\r
1378             this.menu.menu = filter.menu;\r
1379             this.menu.setChecked(filter.active, false);\r
1380             // disable the menu if filter.disabled explicitly set to true\r
1381             this.menu.setDisabled(filter.disabled === true);\r
1382         }\r
1383         \r
1384         this.menu.setVisible(filter !== undefined);\r
1385         this.sep.setVisible(filter !== undefined);\r
1386     },\r
1387     \r
1388     /** @private */\r
1389     onCheckChange : function (item, value) {\r
1390         this.getMenuFilter().setActive(value);\r
1391     },\r
1392     \r
1393     /** @private */\r
1394     onBeforeCheck : function (check, value) {\r
1395         return !value || this.getMenuFilter().isActivatable();\r
1396     },\r
1397 \r
1398     /**\r
1399      * @private\r
1400      * Handler for all events on filters.\r
1401      * @param {String} event Event name\r
1402      * @param {Object} filter Standard signature of the event before the event is fired\r
1403      */   \r
1404     onStateChange : function (event, filter) {\r
1405         if (event === 'serialize') {\r
1406             return;\r
1407         }\r
1408 \r
1409         if (filter == this.getMenuFilter()) {\r
1410             this.menu.setChecked(filter.active, false);\r
1411         }\r
1412 \r
1413         if ((this.autoReload || this.local) && !this.applyingState) {\r
1414             this.deferredUpdate.delay(this.updateBuffer);\r
1415         }\r
1416         this.updateColumnHeadings();\r
1417             \r
1418         if (!this.applyingState) {\r
1419             this.grid.saveState();\r
1420         }    \r
1421         this.grid.fireEvent('filterupdate', this, filter);\r
1422     },\r
1423     \r
1424     /**\r
1425      * @private\r
1426      * Handler for store's beforeload event when configured for remote filtering\r
1427      * @param {Object} store\r
1428      * @param {Object} options\r
1429      */\r
1430     onBeforeLoad : function (store, options) {\r
1431         options.params = options.params || {};\r
1432         this.cleanParams(options.params);               \r
1433         var params = this.buildQuery(this.getFilterData());\r
1434         Ext.apply(options.params, params);\r
1435     },\r
1436     \r
1437     /**\r
1438      * @private\r
1439      * Handler for store's load event when configured for local filtering\r
1440      * @param {Object} store\r
1441      * @param {Object} options\r
1442      */\r
1443     onLoad : function (store, options) {\r
1444         store.filterBy(this.getRecordFilter());\r
1445     },\r
1446 \r
1447     /**\r
1448      * @private\r
1449      * Handler called when the grid's view is refreshed\r
1450      */    \r
1451     onRefresh : function () {\r
1452         this.updateColumnHeadings();\r
1453     },\r
1454 \r
1455     /**\r
1456      * Update the styles for the header row based on the active filters\r
1457      */    \r
1458     updateColumnHeadings : function () {\r
1459         var view = this.grid.getView(),\r
1460             hds, i, len, filter;\r
1461         if (view.mainHd) {\r
1462             hds = view.mainHd.select('td').removeClass(this.filterCls);\r
1463             for (i = 0, len = view.cm.config.length; i < len; i++) {\r
1464                 filter = this.getFilter(view.cm.config[i].dataIndex);\r
1465                 if (filter && filter.active) {\r
1466                     hds.item(i).addClass(this.filterCls);\r
1467                 }\r
1468             }\r
1469         }\r
1470     },\r
1471     \r
1472     /** @private */\r
1473     reload : function () {\r
1474         if (this.local) {\r
1475             this.grid.store.clearFilter(true);\r
1476             this.grid.store.filterBy(this.getRecordFilter());\r
1477         } else {\r
1478             var start,\r
1479                 store = this.grid.store;\r
1480             this.deferredUpdate.cancel();\r
1481             if (this.toolbar) {\r
1482                 start = store.paramNames.start;\r
1483                 if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {\r
1484                     store.lastOptions.params[start] = 0;\r
1485                 }\r
1486             }\r
1487             store.reload();\r
1488         }\r
1489     },\r
1490     \r
1491     /**\r
1492      * Method factory that generates a record validator for the filters active at the time\r
1493      * of invokation.\r
1494      * @private\r
1495      */\r
1496     getRecordFilter : function () {\r
1497         var f = [], len, i;\r
1498         this.filters.each(function (filter) {\r
1499             if (filter.active) {\r
1500                 f.push(filter);\r
1501             }\r
1502         });\r
1503         \r
1504         len = f.length;\r
1505         return function (record) {\r
1506             for (i = 0; i < len; i++) {\r
1507                 if (!f[i].validateRecord(record)) {\r
1508                     return false;\r
1509                 }\r
1510             }\r
1511             return true;\r
1512         };\r
1513     },\r
1514     \r
1515     /**\r
1516      * Adds a filter to the collection and observes it for state change.\r
1517      * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.\r
1518      * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.\r
1519      */\r
1520     addFilter : function (config) {\r
1521         var Cls = this.getFilterClass(config.type),\r
1522             filter = config.menu ? config : (new Cls(config));\r
1523         this.filters.add(filter);\r
1524         \r
1525         Ext.util.Observable.capture(filter, this.onStateChange, this);\r
1526         return filter;\r
1527     },\r
1528 \r
1529     /**\r
1530      * Adds filters to the collection.\r
1531      * @param {Array/Ext.grid.ColumnModel} filters Either an Array of\r
1532      * filter configuration objects or an Ext.grid.ColumnModel.  The columns\r
1533      * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>\r
1534      * property and, if present, will be used as the filter configuration object.   \r
1535      */\r
1536     addFilters : function (filters) {\r
1537         if (filters) {\r
1538             var i, len, filter, cm = false, dI;\r
1539             if (filters instanceof Ext.grid.ColumnModel) {\r
1540                 filters = filters.config;\r
1541                 cm = true;\r
1542             }\r
1543             for (i = 0, len = filters.length; i < len; i++) {\r
1544                 filter = false;\r
1545                 if (cm) {\r
1546                     dI = filters[i].dataIndex;\r
1547                     filter = filters[i].filter || filters[i].filterable;\r
1548                     if (filter){\r
1549                         filter = (filter === true) ? {} : filter;\r
1550                         Ext.apply(filter, {dataIndex:dI});\r
1551                         // filter type is specified in order of preference:\r
1552                         //     filter type specified in config\r
1553                         //     type specified in store's field's type config\r
1554                         filter.type = filter.type || this.store.fields.get(dI).type;  \r
1555                     }\r
1556                 } else {\r
1557                     filter = filters[i];\r
1558                 }\r
1559                 // if filter config found add filter for the column \r
1560                 if (filter) {\r
1561                     this.addFilter(filter);\r
1562                 }\r
1563             }\r
1564         }\r
1565     },\r
1566     \r
1567     /**\r
1568      * Returns a filter for the given dataIndex, if one exists.\r
1569      * @param {String} dataIndex The dataIndex of the desired filter object.\r
1570      * @return {Ext.ux.grid.filter.Filter}\r
1571      */\r
1572     getFilter : function (dataIndex) {\r
1573         return this.filters.get(dataIndex);\r
1574     },\r
1575 \r
1576     /**\r
1577      * Turns all filters off. This does not clear the configuration information\r
1578      * (see {@link #removeAll}).\r
1579      */\r
1580     clearFilters : function () {\r
1581         this.filters.each(function (filter) {\r
1582             filter.setActive(false);\r
1583         });\r
1584     },\r
1585 \r
1586     /**\r
1587      * Returns an Array of the currently active filters.\r
1588      * @return {Array} filters Array of the currently active filters.\r
1589      */\r
1590     getFilterData : function () {\r
1591         var filters = [], i, len;\r
1592 \r
1593         this.filters.each(function (f) {\r
1594             if (f.active) {\r
1595                 var d = [].concat(f.serialize());\r
1596                 for (i = 0, len = d.length; i < len; i++) {\r
1597                     filters.push({\r
1598                         field: f.dataIndex,\r
1599                         data: d[i]\r
1600                     });\r
1601                 }\r
1602             }\r
1603         });\r
1604         return filters;\r
1605     },\r
1606     \r
1607     /**\r
1608      * Function to take the active filters data and build it into a query.\r
1609      * The format of the query depends on the <code>{@link #encode}</code>\r
1610      * configuration:\r
1611      * <div class="mdetail-params"><ul>\r
1612      * \r
1613      * <li><b><tt>false</tt></b> : <i>Default</i>\r
1614      * <div class="sub-desc">\r
1615      * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:\r
1616      * <pre><code>\r
1617 filters[0][field]="someDataIndex"&\r
1618 filters[0][data][comparison]="someValue1"&\r
1619 filters[0][data][type]="someValue2"&\r
1620 filters[0][data][value]="someValue3"&\r
1621      * </code></pre>\r
1622      * </div></li>\r
1623      * <li><b><tt>true</tt></b> : \r
1624      * <div class="sub-desc">\r
1625      * JSON encode the filter data\r
1626      * <pre><code>\r
1627 filters[0][field]="someDataIndex"&\r
1628 filters[0][data][comparison]="someValue1"&\r
1629 filters[0][data][type]="someValue2"&\r
1630 filters[0][data][value]="someValue3"&\r
1631      * </code></pre>\r
1632      * </div></li>\r
1633      * </ul></div>\r
1634      * Override this method to customize the format of the filter query for remote requests.\r
1635      * @param {Array} filters A collection of objects representing active filters and their configuration.\r
1636      *    Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured\r
1637      *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.\r
1638      * @return {Object} Query keys and values\r
1639      */\r
1640     buildQuery : function (filters) {\r
1641         var p = {}, i, f, root, dataPrefix, key, tmp,\r
1642             len = filters.length;\r
1643 \r
1644         if (!this.encode){\r
1645             for (i = 0; i < len; i++) {\r
1646                 f = filters[i];\r
1647                 root = [this.paramPrefix, '[', i, ']'].join('');\r
1648                 p[root + '[field]'] = f.field;\r
1649                 \r
1650                 dataPrefix = root + '[data]';\r
1651                 for (key in f.data) {\r
1652                     p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];\r
1653                 }\r
1654             }\r
1655         } else {\r
1656             tmp = [];\r
1657             for (i = 0; i < len; i++) {\r
1658                 f = filters[i];\r
1659                 tmp.push(Ext.apply(\r
1660                     {},\r
1661                     {field: f.field},\r
1662                     f.data\r
1663                 ));\r
1664             }\r
1665             // only build if there is active filter \r
1666             if (tmp.length > 0){\r
1667                 p[this.paramPrefix] = Ext.util.JSON.encode(tmp);\r
1668             }\r
1669         }\r
1670         return p;\r
1671     },\r
1672     \r
1673     /**\r
1674      * Removes filter related query parameters from the provided object.\r
1675      * @param {Object} p Query parameters that may contain filter related fields.\r
1676      */\r
1677     cleanParams : function (p) {\r
1678         // if encoding just delete the property\r
1679         if (this.encode) {\r
1680             delete p[this.paramPrefix];\r
1681         // otherwise scrub the object of filter data\r
1682         } else {\r
1683             var regex, key;\r
1684             regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');\r
1685             for (key in p) {\r
1686                 if (regex.test(key)) {\r
1687                     delete p[key];\r
1688                 }\r
1689             }\r
1690         }\r
1691     },\r
1692     \r
1693     /**\r
1694      * Function for locating filter classes, overwrite this with your favorite\r
1695      * loader to provide dynamic filter loading.\r
1696      * @param {String} type The type of filter to load ('Filter' is automatically\r
1697      * appended to the passed type; eg, 'string' becomes 'StringFilter').\r
1698      * @return {Class} The Ext.ux.grid.filter.Class \r
1699      */\r
1700     getFilterClass : function (type) {\r
1701         // map the supported Ext.data.Field type values into a supported filter\r
1702         switch(type) {\r
1703             case 'auto':\r
1704               type = 'string';\r
1705               break;\r
1706             case 'int':\r
1707             case 'float':\r
1708               type = 'numeric';\r
1709               break;\r
1710         }\r
1711         return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];\r
1712     }\r
1713 });\r
1714 \r
1715 // register ptype\r
1716 Ext.preg('gridfilters', Ext.ux.grid.GridFilters);\r
1717 Ext.namespace('Ext.ux.grid.filter');\r
1718 \r
1719 /** \r
1720  * @class Ext.ux.grid.filter.Filter\r
1721  * @extends Ext.util.Observable\r
1722  * Abstract base class for filter implementations.\r
1723  */\r
1724 Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, {\r
1725     /**\r
1726      * @cfg {Boolean} active\r
1727      * Indicates the initial status of the filter (defaults to false).\r
1728      */\r
1729     active : false,\r
1730     /**\r
1731      * True if this filter is active.  Use setActive() to alter after configuration.\r
1732      * @type Boolean\r
1733      * @property active\r
1734      */\r
1735     /**\r
1736      * @cfg {String} dataIndex \r
1737      * The {@link Ext.data.Store} dataIndex of the field this filter represents.\r
1738      * The dataIndex does not actually have to exist in the store.\r
1739      */\r
1740     dataIndex : null,\r
1741     /**\r
1742      * The filter configuration menu that will be installed into the filter submenu of a column menu.\r
1743      * @type Ext.menu.Menu\r
1744      * @property\r
1745      */\r
1746     menu : null,\r
1747     /**\r
1748      * @cfg {Number} updateBuffer\r
1749      * Number of milliseconds to wait after user interaction to fire an update. Only supported \r
1750      * by filters: 'list', 'numeric', and 'string'. Defaults to 500.\r
1751      */\r
1752     updateBuffer : 500,\r
1753 \r
1754     constructor : function (config) {\r
1755         Ext.apply(this, config);\r
1756             \r
1757         this.addEvents(\r
1758             /**\r
1759              * @event activate\r
1760              * Fires when an inactive filter becomes active\r
1761              * @param {Ext.ux.grid.filter.Filter} this\r
1762              */\r
1763             'activate',\r
1764             /**\r
1765              * @event deactivate\r
1766              * Fires when an active filter becomes inactive\r
1767              * @param {Ext.ux.grid.filter.Filter} this\r
1768              */\r
1769             'deactivate',\r
1770             /**\r
1771              * @event serialize\r
1772              * Fires after the serialization process. Use this to attach additional parameters to serialization\r
1773              * data before it is encoded and sent to the server.\r
1774              * @param {Array/Object} data A map or collection of maps representing the current filter configuration.\r
1775              * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.\r
1776              */\r
1777             'serialize',\r
1778             /**\r
1779              * @event update\r
1780              * Fires when a filter configuration has changed\r
1781              * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
1782              */\r
1783             'update'\r
1784         );\r
1785         Ext.ux.grid.filter.Filter.superclass.constructor.call(this);\r
1786 \r
1787         this.menu = new Ext.menu.Menu();\r
1788         this.init(config);\r
1789         if(config && config.value){\r
1790             this.setValue(config.value);\r
1791             this.setActive(config.active !== false, true);\r
1792             delete config.value;\r
1793         }\r
1794     },\r
1795 \r
1796     /**\r
1797      * Destroys this filter by purging any event listeners, and removing any menus.\r
1798      */\r
1799     destroy : function(){\r
1800         if (this.menu){\r
1801             this.menu.destroy();\r
1802         }\r
1803         this.purgeListeners();\r
1804     },\r
1805 \r
1806     /**\r
1807      * Template method to be implemented by all subclasses that is to\r
1808      * initialize the filter and install required menu items.\r
1809      * Defaults to Ext.emptyFn.\r
1810      */\r
1811     init : Ext.emptyFn,\r
1812     \r
1813     /**\r
1814      * Template method to be implemented by all subclasses that is to\r
1815      * get and return the value of the filter.\r
1816      * Defaults to Ext.emptyFn.\r
1817      * @return {Object} The 'serialized' form of this filter\r
1818      * @methodOf Ext.ux.grid.filter.Filter\r
1819      */\r
1820     getValue : Ext.emptyFn,\r
1821     \r
1822     /**\r
1823      * Template method to be implemented by all subclasses that is to\r
1824      * set the value of the filter and fire the 'update' event.\r
1825      * Defaults to Ext.emptyFn.\r
1826      * @param {Object} data The value to set the filter\r
1827      * @methodOf Ext.ux.grid.filter.Filter\r
1828      */ \r
1829     setValue : Ext.emptyFn,\r
1830     \r
1831     /**\r
1832      * Template method to be implemented by all subclasses that is to\r
1833      * return <tt>true</tt> if the filter has enough configuration information to be activated.\r
1834      * Defaults to <tt>return true</tt>.\r
1835      * @return {Boolean}\r
1836      */\r
1837     isActivatable : function(){\r
1838         return true;\r
1839     },\r
1840     \r
1841     /**\r
1842      * Template method to be implemented by all subclasses that is to\r
1843      * get and return serialized filter data for transmission to the server.\r
1844      * Defaults to Ext.emptyFn.\r
1845      */\r
1846     getSerialArgs : Ext.emptyFn,\r
1847 \r
1848     /**\r
1849      * Template method to be implemented by all subclasses that is to\r
1850      * validates the provided Ext.data.Record against the filters configuration.\r
1851      * Defaults to <tt>return true</tt>.\r
1852      * @param {Ext.data.Record} record The record to validate\r
1853      * @return {Boolean} true if the record is valid within the bounds\r
1854      * of the filter, false otherwise.\r
1855      */\r
1856     validateRecord : function(){\r
1857         return true;\r
1858     },\r
1859 \r
1860     /**\r
1861      * Returns the serialized filter data for transmission to the server\r
1862      * and fires the 'serialize' event.\r
1863      * @return {Object/Array} An object or collection of objects containing\r
1864      * key value pairs representing the current configuration of the filter.\r
1865      * @methodOf Ext.ux.grid.filter.Filter\r
1866      */\r
1867     serialize : function(){\r
1868         var args = this.getSerialArgs();\r
1869         this.fireEvent('serialize', args, this);\r
1870         return args;\r
1871     },\r
1872 \r
1873     /** @private */\r
1874     fireUpdate : function(){\r
1875         if (this.active) {\r
1876             this.fireEvent('update', this);\r
1877         }\r
1878         this.setActive(this.isActivatable());\r
1879     },\r
1880     \r
1881     /**\r
1882      * Sets the status of the filter and fires the appropriate events.\r
1883      * @param {Boolean} active        The new filter state.\r
1884      * @param {Boolean} suppressEvent True to prevent events from being fired.\r
1885      * @methodOf Ext.ux.grid.filter.Filter\r
1886      */\r
1887     setActive : function(active, suppressEvent){\r
1888         if(this.active != active){\r
1889             this.active = active;\r
1890             if (suppressEvent !== true) {\r
1891                 this.fireEvent(active ? 'activate' : 'deactivate', this);\r
1892             }\r
1893         }\r
1894     }    \r
1895 });/** \r
1896  * @class Ext.ux.grid.filter.BooleanFilter\r
1897  * @extends Ext.ux.grid.filter.Filter\r
1898  * Boolean filters use unique radio group IDs (so you can have more than one!)\r
1899  * <p><b><u>Example Usage:</u></b></p>\r
1900  * <pre><code>    \r
1901 var filters = new Ext.ux.grid.GridFilters({\r
1902     ...\r
1903     filters: [{\r
1904         // required configs\r
1905         type: 'boolean',\r
1906         dataIndex: 'visible'\r
1907 \r
1908         // optional configs\r
1909         defaultValue: null, // leave unselected (false selected by default)\r
1910         yesText: 'Yes',     // default\r
1911         noText: 'No'        // default\r
1912     }]\r
1913 });\r
1914  * </code></pre>\r
1915  */\r
1916 Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
1917         /**\r
1918          * @cfg {Boolean} defaultValue\r
1919          * Set this to null if you do not want either option to be checked by default. Defaults to false.\r
1920          */\r
1921         defaultValue : false,\r
1922         /**\r
1923          * @cfg {String} yesText\r
1924          * Defaults to 'Yes'.\r
1925          */\r
1926         yesText : 'Yes',\r
1927         /**\r
1928          * @cfg {String} noText\r
1929          * Defaults to 'No'.\r
1930          */\r
1931         noText : 'No',\r
1932 \r
1933     /**  \r
1934      * @private\r
1935      * Template method that is to initialize the filter and install required menu items.\r
1936      */\r
1937     init : function (config) {\r
1938         var gId = Ext.id();\r
1939                 this.options = [\r
1940                         new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}),\r
1941                         new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})];\r
1942                 \r
1943                 this.menu.add(this.options[0], this.options[1]);\r
1944                 \r
1945                 for(var i=0; i<this.options.length; i++){\r
1946                         this.options[i].on('click', this.fireUpdate, this);\r
1947                         this.options[i].on('checkchange', this.fireUpdate, this);\r
1948                 }\r
1949         },\r
1950         \r
1951     /**\r
1952      * @private\r
1953      * Template method that is to get and return the value of the filter.\r
1954      * @return {String} The value of this filter\r
1955      */\r
1956     getValue : function () {\r
1957                 return this.options[0].checked;\r
1958         },\r
1959 \r
1960     /**\r
1961      * @private\r
1962      * Template method that is to set the value of the filter.\r
1963      * @param {Object} value The value to set the filter\r
1964      */ \r
1965         setValue : function (value) {\r
1966                 this.options[value ? 0 : 1].setChecked(true);\r
1967         },\r
1968 \r
1969     /**\r
1970      * @private\r
1971      * Template method that is to get and return serialized filter data for\r
1972      * transmission to the server.\r
1973      * @return {Object/Array} An object or collection of objects containing\r
1974      * key value pairs representing the current configuration of the filter.\r
1975      */\r
1976     getSerialArgs : function () {\r
1977                 var args = {type: 'boolean', value: this.getValue()};\r
1978                 return args;\r
1979         },\r
1980         \r
1981     /**\r
1982      * Template method that is to validate the provided Ext.data.Record\r
1983      * against the filters configuration.\r
1984      * @param {Ext.data.Record} record The record to validate\r
1985      * @return {Boolean} true if the record is valid within the bounds\r
1986      * of the filter, false otherwise.\r
1987      */\r
1988     validateRecord : function (record) {\r
1989                 return record.get(this.dataIndex) == this.getValue();\r
1990         }\r
1991 });/** \r
1992  * @class Ext.ux.grid.filter.DateFilter\r
1993  * @extends Ext.ux.grid.filter.Filter\r
1994  * Filter by a configurable Ext.menu.DateMenu\r
1995  * <p><b><u>Example Usage:</u></b></p>\r
1996  * <pre><code>    \r
1997 var filters = new Ext.ux.grid.GridFilters({\r
1998     ...\r
1999     filters: [{\r
2000         // required configs\r
2001         type: 'date',\r
2002         dataIndex: 'dateAdded',\r
2003         \r
2004         // optional configs\r
2005         dateFormat: 'm/d/Y',  // default\r
2006         beforeText: 'Before', // default\r
2007         afterText: 'After',   // default\r
2008         onText: 'On',         // default\r
2009         pickerOpts: {\r
2010             // any DateMenu configs\r
2011         },\r
2012 \r
2013         active: true // default is false\r
2014     }]\r
2015 });\r
2016  * </code></pre>\r
2017  */\r
2018 Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2019     /**\r
2020      * @cfg {String} afterText\r
2021      * Defaults to 'After'.\r
2022      */\r
2023     afterText : 'After',\r
2024     /**\r
2025      * @cfg {String} beforeText\r
2026      * Defaults to 'Before'.\r
2027      */\r
2028     beforeText : 'Before',\r
2029     /**\r
2030      * @cfg {Object} compareMap\r
2031      * Map for assigning the comparison values used in serialization.\r
2032      */\r
2033     compareMap : {\r
2034         before: 'lt',\r
2035         after:  'gt',\r
2036         on:     'eq'\r
2037     },\r
2038     /**\r
2039      * @cfg {String} dateFormat\r
2040      * The date format to return when using getValue.\r
2041      * Defaults to 'm/d/Y'.\r
2042      */\r
2043     dateFormat : 'm/d/Y',\r
2044 \r
2045     /**\r
2046      * @cfg {Date} maxDate\r
2047      * Allowable date as passed to the Ext.DatePicker\r
2048      * Defaults to undefined.\r
2049      */\r
2050     /**\r
2051      * @cfg {Date} minDate\r
2052      * Allowable date as passed to the Ext.DatePicker\r
2053      * Defaults to undefined.\r
2054      */\r
2055     /**\r
2056      * @cfg {Array} menuItems\r
2057      * The items to be shown in this menu\r
2058      * Defaults to:<pre>\r
2059      * menuItems : ['before', 'after', '-', 'on'],\r
2060      * </pre>\r
2061      */\r
2062     menuItems : ['before', 'after', '-', 'on'],\r
2063 \r
2064     /**\r
2065      * @cfg {Object} menuItemCfgs\r
2066      * Default configuration options for each menu item\r
2067      */\r
2068     menuItemCfgs : {\r
2069         selectOnFocus: true,\r
2070         width: 125\r
2071     },\r
2072 \r
2073     /**\r
2074      * @cfg {String} onText\r
2075      * Defaults to 'On'.\r
2076      */\r
2077     onText : 'On',\r
2078     \r
2079     /**\r
2080      * @cfg {Object} pickerOpts\r
2081      * Configuration options for the date picker associated with each field.\r
2082      */\r
2083     pickerOpts : {},\r
2084 \r
2085     /**  \r
2086      * @private\r
2087      * Template method that is to initialize the filter and install required menu items.\r
2088      */\r
2089     init : function (config) {\r
2090         var menuCfg, i, len, item, cfg, Cls;\r
2091 \r
2092         menuCfg = Ext.apply(this.pickerOpts, {\r
2093             minDate: this.minDate, \r
2094             maxDate: this.maxDate, \r
2095             format:  this.dateFormat,\r
2096             listeners: {\r
2097                 scope: this,\r
2098                 select: this.onMenuSelect\r
2099             }\r
2100         });\r
2101 \r
2102         this.fields = {};\r
2103         for (i = 0, len = this.menuItems.length; i < len; i++) {\r
2104             item = this.menuItems[i];\r
2105             if (item !== '-') {\r
2106                 cfg = {\r
2107                     itemId: 'range-' + item,\r
2108                     text: this[item + 'Text'],\r
2109                     menu: new Ext.menu.DateMenu(\r
2110                         Ext.apply(menuCfg, {\r
2111                             itemId: item\r
2112                         })\r
2113                     ),\r
2114                     listeners: {\r
2115                         scope: this,\r
2116                         checkchange: this.onCheckChange\r
2117                     }\r
2118                 };\r
2119                 Cls = Ext.menu.CheckItem;\r
2120                 item = this.fields[item] = new Cls(cfg);\r
2121             }\r
2122             //this.add(item);\r
2123             this.menu.add(item);\r
2124         }\r
2125     },\r
2126 \r
2127     onCheckChange : function () {\r
2128         this.setActive(this.isActivatable());\r
2129         this.fireEvent('update', this);\r
2130     },\r
2131 \r
2132     /**  \r
2133      * @private\r
2134      * Handler method called when there is a keyup event on an input\r
2135      * item of this menu.\r
2136      */\r
2137     onInputKeyUp : function (field, e) {\r
2138         var k = e.getKey();\r
2139         if (k == e.RETURN && field.isValid()) {\r
2140             e.stopEvent();\r
2141             this.menu.hide(true);\r
2142             return;\r
2143         }\r
2144     },\r
2145 \r
2146     /**\r
2147      * Handler for when the menu for a field fires the 'select' event\r
2148      * @param {Object} date\r
2149      * @param {Object} menuItem\r
2150      * @param {Object} value\r
2151      * @param {Object} picker\r
2152      */\r
2153     onMenuSelect : function (menuItem, value, picker) {\r
2154         var fields = this.fields,\r
2155             field = this.fields[menuItem.itemId];\r
2156         \r
2157         field.setChecked(true);\r
2158         \r
2159         if (field == fields.on) {\r
2160             fields.before.setChecked(false, true);\r
2161             fields.after.setChecked(false, true);\r
2162         } else {\r
2163             fields.on.setChecked(false, true);\r
2164             if (field == fields.after && fields.before.menu.picker.value < value) {\r
2165                 fields.before.setChecked(false, true);\r
2166             } else if (field == fields.before && fields.after.menu.picker.value > value) {\r
2167                 fields.after.setChecked(false, true);\r
2168             }\r
2169         }\r
2170         this.fireEvent('update', this);\r
2171     },\r
2172 \r
2173     /**\r
2174      * @private\r
2175      * Template method that is to get and return the value of the filter.\r
2176      * @return {String} The value of this filter\r
2177      */\r
2178     getValue : function () {\r
2179         var key, result = {};\r
2180         for (key in this.fields) {\r
2181             if (this.fields[key].checked) {\r
2182                 result[key] = this.fields[key].menu.picker.getValue();\r
2183             }\r
2184         }\r
2185         return result;\r
2186     },\r
2187 \r
2188     /**\r
2189      * @private\r
2190      * Template method that is to set the value of the filter.\r
2191      * @param {Object} value The value to set the filter\r
2192      * @param {Boolean} preserve true to preserve the checked status\r
2193      * of the other fields.  Defaults to false, unchecking the\r
2194      * other fields\r
2195      */ \r
2196     setValue : function (value, preserve) {\r
2197         var key;\r
2198         for (key in this.fields) {\r
2199             if(value[key]){\r
2200                 this.fields[key].menu.picker.setValue(value[key]);\r
2201                 this.fields[key].setChecked(true);\r
2202             } else if (!preserve) {\r
2203                 this.fields[key].setChecked(false);\r
2204             }\r
2205         }\r
2206         this.fireEvent('update', this);\r
2207     },\r
2208 \r
2209     /**\r
2210      * @private\r
2211      * Template method that is to return <tt>true</tt> if the filter\r
2212      * has enough configuration information to be activated.\r
2213      * @return {Boolean}\r
2214      */\r
2215     isActivatable : function () {\r
2216         var key;\r
2217         for (key in this.fields) {\r
2218             if (this.fields[key].checked) {\r
2219                 return true;\r
2220             }\r
2221         }\r
2222         return false;\r
2223     },\r
2224 \r
2225     /**\r
2226      * @private\r
2227      * Template method that is to get and return serialized filter data for\r
2228      * transmission to the server.\r
2229      * @return {Object/Array} An object or collection of objects containing\r
2230      * key value pairs representing the current configuration of the filter.\r
2231      */\r
2232     getSerialArgs : function () {\r
2233         var args = [];\r
2234         for (var key in this.fields) {\r
2235             if(this.fields[key].checked){\r
2236                 args.push({\r
2237                     type: 'date',\r
2238                     comparison: this.compareMap[key],\r
2239                     value: this.getFieldValue(key).format(this.dateFormat)\r
2240                 });\r
2241             }\r
2242         }\r
2243         return args;\r
2244     },\r
2245 \r
2246     /**\r
2247      * Get and return the date menu picker value\r
2248      * @param {String} item The field identifier ('before', 'after', 'on')\r
2249      * @return {Date} Gets the current selected value of the date field\r
2250      */\r
2251     getFieldValue : function(item){\r
2252         return this.fields[item].menu.picker.getValue();\r
2253     },\r
2254     \r
2255     /**\r
2256      * Gets the menu picker associated with the passed field\r
2257      * @param {String} item The field identifier ('before', 'after', 'on')\r
2258      * @return {Object} The menu picker\r
2259      */\r
2260     getPicker : function(item){\r
2261         return this.fields[item].menu.picker;\r
2262     },\r
2263 \r
2264     /**\r
2265      * Template method that is to validate the provided Ext.data.Record\r
2266      * against the filters configuration.\r
2267      * @param {Ext.data.Record} record The record to validate\r
2268      * @return {Boolean} true if the record is valid within the bounds\r
2269      * of the filter, false otherwise.\r
2270      */\r
2271     validateRecord : function (record) {\r
2272         var key,\r
2273             pickerValue,\r
2274             val = record.get(this.dataIndex);\r
2275             \r
2276         if(!Ext.isDate(val)){\r
2277             return false;\r
2278         }\r
2279         val = val.clearTime(true).getTime();\r
2280         \r
2281         for (key in this.fields) {\r
2282             if (this.fields[key].checked) {\r
2283                 pickerValue = this.getFieldValue(key).clearTime(true).getTime();\r
2284                 if (key == 'before' && pickerValue <= val) {\r
2285                     return false;\r
2286                 }\r
2287                 if (key == 'after' && pickerValue >= val) {\r
2288                     return false;\r
2289                 }\r
2290                 if (key == 'on' && pickerValue != val) {\r
2291                     return false;\r
2292                 }\r
2293             }\r
2294         }\r
2295         return true;\r
2296     }\r
2297 });/** \r
2298  * @class Ext.ux.grid.filter.ListFilter\r
2299  * @extends Ext.ux.grid.filter.Filter\r
2300  * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load\r
2301  * their options the first time they are shown. ListFilter utilizes the\r
2302  * {@link Ext.ux.menu.ListMenu} component.</p>\r
2303  * <p>Although not shown here, this class accepts all configuration options\r
2304  * for {@link Ext.ux.menu.ListMenu}.</p>\r
2305  * \r
2306  * <p><b><u>Example Usage:</u></b></p>\r
2307  * <pre><code>    \r
2308 var filters = new Ext.ux.grid.GridFilters({\r
2309     ...\r
2310     filters: [{\r
2311         type: 'list',\r
2312         dataIndex: 'size',\r
2313         phpMode: true,\r
2314         // options will be used as data to implicitly creates an ArrayStore\r
2315         options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
2316     }]\r
2317 });\r
2318  * </code></pre>\r
2319  * \r
2320  */\r
2321 Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2322 \r
2323     /**\r
2324      * @cfg {Array} options\r
2325      * <p><code>data</code> to be used to implicitly create a data store\r
2326      * to back this list when the data source is <b>local</b>. If the\r
2327      * data for the list is remote, use the <code>{@link #store}</code>\r
2328      * config instead.</p>\r
2329      * <br><p>Each item within the provided array may be in one of the\r
2330      * following formats:</p>\r
2331      * <div class="mdetail-params"><ul>\r
2332      * <li><b>Array</b> :\r
2333      * <pre><code>\r
2334 options: [\r
2335     [11, 'extra small'], \r
2336     [18, 'small'],\r
2337     [22, 'medium'],\r
2338     [35, 'large'],\r
2339     [44, 'extra large']\r
2340 ]\r
2341      * </code></pre>\r
2342      * </li>\r
2343      * <li><b>Object</b> :\r
2344      * <pre><code>\r
2345 labelField: 'name', // override default of 'text'\r
2346 options: [\r
2347     {id: 11, name:'extra small'}, \r
2348     {id: 18, name:'small'}, \r
2349     {id: 22, name:'medium'}, \r
2350     {id: 35, name:'large'}, \r
2351     {id: 44, name:'extra large'} \r
2352 ]\r
2353      * </code></pre>\r
2354      * </li>\r
2355      * <li><b>String</b> :\r
2356      * <pre><code>\r
2357      * options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
2358      * </code></pre>\r
2359      * </li>\r
2360      */\r
2361     /**\r
2362      * @cfg {Boolean} phpMode\r
2363      * <p>Adjust the format of this filter. Defaults to false.</p>\r
2364      * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>\r
2365      * <pre><code>\r
2366 // phpMode == false (default):\r
2367 filter[0][data][type] list\r
2368 filter[0][data][value] value1\r
2369 filter[0][data][value] value2\r
2370 filter[0][field] prod \r
2371 \r
2372 // phpMode == true:\r
2373 filter[0][data][type] list\r
2374 filter[0][data][value] value1, value2\r
2375 filter[0][field] prod \r
2376      * </code></pre>\r
2377      * When GridFilters <code>@cfg encode = true</code>:\r
2378      * <pre><code>\r
2379 // phpMode == false (default):\r
2380 filter : [{"type":"list","value":["small","medium"],"field":"size"}]\r
2381 \r
2382 // phpMode == true:\r
2383 filter : [{"type":"list","value":"small,medium","field":"size"}]\r
2384      * </code></pre>\r
2385      */\r
2386     phpMode : false,\r
2387     /**\r
2388      * @cfg {Ext.data.Store} store\r
2389      * The {@link Ext.data.Store} this list should use as its data source\r
2390      * when the data source is <b>remote</b>. If the data for the list\r
2391      * is local, use the <code>{@link #options}</code> config instead.\r
2392      */\r
2393 \r
2394     /**  \r
2395      * @private\r
2396      * Template method that is to initialize the filter and install required menu items.\r
2397      * @param {Object} config\r
2398      */\r
2399     init : function (config) {\r
2400         this.dt = new Ext.util.DelayedTask(this.fireUpdate, this);\r
2401 \r
2402         // if a menu already existed, do clean up first\r
2403         if (this.menu){\r
2404             this.menu.destroy();\r
2405         }\r
2406         this.menu = new Ext.ux.menu.ListMenu(config);\r
2407         this.menu.on('checkchange', this.onCheckChange, this);\r
2408     },\r
2409     \r
2410     /**\r
2411      * @private\r
2412      * Template method that is to get and return the value of the filter.\r
2413      * @return {String} The value of this filter\r
2414      */\r
2415     getValue : function () {\r
2416         return this.menu.getSelected();\r
2417     },\r
2418     /**\r
2419      * @private\r
2420      * Template method that is to set the value of the filter.\r
2421      * @param {Object} value The value to set the filter\r
2422      */ \r
2423     setValue : function (value) {\r
2424         this.menu.setSelected(value);\r
2425         this.fireEvent('update', this);\r
2426     },\r
2427 \r
2428     /**\r
2429      * @private\r
2430      * Template method that is to return <tt>true</tt> if the filter\r
2431      * has enough configuration information to be activated.\r
2432      * @return {Boolean}\r
2433      */\r
2434     isActivatable : function () {\r
2435         return this.getValue().length > 0;\r
2436     },\r
2437     \r
2438     /**\r
2439      * @private\r
2440      * Template method that is to get and return serialized filter data for\r
2441      * transmission to the server.\r
2442      * @return {Object/Array} An object or collection of objects containing\r
2443      * key value pairs representing the current configuration of the filter.\r
2444      */\r
2445     getSerialArgs : function () {\r
2446         var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()};\r
2447         return args;\r
2448     },\r
2449 \r
2450     /** @private */\r
2451     onCheckChange : function(){\r
2452         this.dt.delay(this.updateBuffer);\r
2453     },\r
2454     \r
2455     \r
2456     /**\r
2457      * Template method that is to validate the provided Ext.data.Record\r
2458      * against the filters configuration.\r
2459      * @param {Ext.data.Record} record The record to validate\r
2460      * @return {Boolean} true if the record is valid within the bounds\r
2461      * of the filter, false otherwise.\r
2462      */\r
2463     validateRecord : function (record) {\r
2464         return this.getValue().indexOf(record.get(this.dataIndex)) > -1;\r
2465     }\r
2466 });/** \r
2467  * @class Ext.ux.grid.filter.NumericFilter\r
2468  * @extends Ext.ux.grid.filter.Filter\r
2469  * Filters using an Ext.ux.menu.RangeMenu.\r
2470  * <p><b><u>Example Usage:</u></b></p>\r
2471  * <pre><code>    \r
2472 var filters = new Ext.ux.grid.GridFilters({\r
2473     ...\r
2474     filters: [{\r
2475         type: 'numeric',\r
2476         dataIndex: 'price'\r
2477     }]\r
2478 });\r
2479  * </code></pre> \r
2480  */\r
2481 Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2482 \r
2483     /**\r
2484      * @cfg {Object} fieldCls\r
2485      * The Class to use to construct each field item within this menu\r
2486      * Defaults to:<pre>\r
2487      * fieldCls : Ext.form.NumberField\r
2488      * </pre>\r
2489      */\r
2490     fieldCls : Ext.form.NumberField,\r
2491     /**\r
2492      * @cfg {Object} fieldCfg\r
2493      * The default configuration options for any field item unless superseded\r
2494      * by the <code>{@link #fields}</code> configuration.\r
2495      * Defaults to:<pre>\r
2496      * fieldCfg : {}\r
2497      * </pre>\r
2498      * Example usage:\r
2499      * <pre><code>\r
2500 fieldCfg : {\r
2501     width: 150,\r
2502 },\r
2503      * </code></pre>\r
2504      */\r
2505     /**\r
2506      * @cfg {Object} fields\r
2507      * The field items may be configured individually\r
2508      * Defaults to <tt>undefined</tt>.\r
2509      * Example usage:\r
2510      * <pre><code>\r
2511 fields : {\r
2512     gt: { // override fieldCfg options\r
2513         width: 200,\r
2514         fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}\r
2515     }\r
2516 },\r
2517      * </code></pre>\r
2518      */\r
2519     /**\r
2520      * @cfg {Object} iconCls\r
2521      * The iconCls to be applied to each comparator field item.\r
2522      * Defaults to:<pre>\r
2523 iconCls : {\r
2524     gt : 'ux-rangemenu-gt',\r
2525     lt : 'ux-rangemenu-lt',\r
2526     eq : 'ux-rangemenu-eq'\r
2527 }\r
2528      * </pre>\r
2529      */\r
2530     iconCls : {\r
2531         gt : 'ux-rangemenu-gt',\r
2532         lt : 'ux-rangemenu-lt',\r
2533         eq : 'ux-rangemenu-eq'\r
2534     },\r
2535 \r
2536     /**\r
2537      * @cfg {Object} menuItemCfgs\r
2538      * Default configuration options for each menu item\r
2539      * Defaults to:<pre>\r
2540 menuItemCfgs : {\r
2541     emptyText: 'Enter Filter Text...',\r
2542     selectOnFocus: true,\r
2543     width: 125\r
2544 }\r
2545      * </pre>\r
2546      */\r
2547     menuItemCfgs : {\r
2548         emptyText: 'Enter Filter Text...',\r
2549         selectOnFocus: true,\r
2550         width: 125\r
2551     },\r
2552 \r
2553     /**\r
2554      * @cfg {Array} menuItems\r
2555      * The items to be shown in this menu.  Items are added to the menu\r
2556      * according to their position within this array. Defaults to:<pre>\r
2557      * menuItems : ['lt','gt','-','eq']\r
2558      * </pre>\r
2559      */\r
2560     menuItems : ['lt', 'gt', '-', 'eq'],\r
2561 \r
2562     /**  \r
2563      * @private\r
2564      * Template method that is to initialize the filter and install required menu items.\r
2565      */\r
2566     init : function (config) {\r
2567         // if a menu already existed, do clean up first\r
2568         if (this.menu){\r
2569             this.menu.destroy();\r
2570         }        \r
2571         this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, {\r
2572             // pass along filter configs to the menu\r
2573             fieldCfg : this.fieldCfg || {},\r
2574             fieldCls : this.fieldCls,\r
2575             fields : this.fields || {},\r
2576             iconCls: this.iconCls,\r
2577             menuItemCfgs: this.menuItemCfgs,\r
2578             menuItems: this.menuItems,\r
2579             updateBuffer: this.updateBuffer\r
2580         }));\r
2581         // relay the event fired by the menu\r
2582         this.menu.on('update', this.fireUpdate, this);\r
2583     },\r
2584     \r
2585     /**\r
2586      * @private\r
2587      * Template method that is to get and return the value of the filter.\r
2588      * @return {String} The value of this filter\r
2589      */\r
2590     getValue : function () {\r
2591         return this.menu.getValue();\r
2592     },\r
2593 \r
2594     /**\r
2595      * @private\r
2596      * Template method that is to set the value of the filter.\r
2597      * @param {Object} value The value to set the filter\r
2598      */ \r
2599     setValue : function (value) {\r
2600         this.menu.setValue(value);\r
2601     },\r
2602 \r
2603     /**\r
2604      * @private\r
2605      * Template method that is to return <tt>true</tt> if the filter\r
2606      * has enough configuration information to be activated.\r
2607      * @return {Boolean}\r
2608      */\r
2609     isActivatable : function () {\r
2610         var values = this.getValue();\r
2611         for (key in values) {\r
2612             if (values[key] !== undefined) {\r
2613                 return true;\r
2614             }\r
2615         }\r
2616         return false;\r
2617     },\r
2618     \r
2619     /**\r
2620      * @private\r
2621      * Template method that is to get and return serialized filter data for\r
2622      * transmission to the server.\r
2623      * @return {Object/Array} An object or collection of objects containing\r
2624      * key value pairs representing the current configuration of the filter.\r
2625      */\r
2626     getSerialArgs : function () {\r
2627         var key,\r
2628             args = [],\r
2629             values = this.menu.getValue();\r
2630         for (key in values) {\r
2631             args.push({\r
2632                 type: 'numeric',\r
2633                 comparison: key,\r
2634                 value: values[key]\r
2635             });\r
2636         }\r
2637         return args;\r
2638     },\r
2639 \r
2640     /**\r
2641      * Template method that is to validate the provided Ext.data.Record\r
2642      * against the filters configuration.\r
2643      * @param {Ext.data.Record} record The record to validate\r
2644      * @return {Boolean} true if the record is valid within the bounds\r
2645      * of the filter, false otherwise.\r
2646      */\r
2647     validateRecord : function (record) {\r
2648         var val = record.get(this.dataIndex),\r
2649             values = this.getValue();\r
2650         if (values.eq !== undefined && val != values.eq) {\r
2651             return false;\r
2652         }\r
2653         if (values.lt !== undefined && val >= values.lt) {\r
2654             return false;\r
2655         }\r
2656         if (values.gt !== undefined && val <= values.gt) {\r
2657             return false;\r
2658         }\r
2659         return true;\r
2660     }\r
2661 });/** \r
2662  * @class Ext.ux.grid.filter.StringFilter\r
2663  * @extends Ext.ux.grid.filter.Filter\r
2664  * Filter by a configurable Ext.form.TextField\r
2665  * <p><b><u>Example Usage:</u></b></p>\r
2666  * <pre><code>    \r
2667 var filters = new Ext.ux.grid.GridFilters({\r
2668     ...\r
2669     filters: [{\r
2670         // required configs\r
2671         type: 'string',\r
2672         dataIndex: 'name',\r
2673         \r
2674         // optional configs\r
2675         value: 'foo',\r
2676         active: true, // default is false\r
2677         iconCls: 'ux-gridfilter-text-icon' // default\r
2678         // any Ext.form.TextField configs accepted\r
2679     }]\r
2680 });\r
2681  * </code></pre>\r
2682  */\r
2683 Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2684 \r
2685     /**\r
2686      * @cfg {String} iconCls\r
2687      * The iconCls to be applied to the menu item.\r
2688      * Defaults to <tt>'ux-gridfilter-text-icon'</tt>.\r
2689      */\r
2690     iconCls : 'ux-gridfilter-text-icon',\r
2691 \r
2692     emptyText: 'Enter Filter Text...',\r
2693     selectOnFocus: true,\r
2694     width: 125,\r
2695     \r
2696     /**  \r
2697      * @private\r
2698      * Template method that is to initialize the filter and install required menu items.\r
2699      */\r
2700     init : function (config) {\r
2701         Ext.applyIf(config, {\r
2702             enableKeyEvents: true,\r
2703             iconCls: this.iconCls,\r
2704             listeners: {\r
2705                 scope: this,\r
2706                 keyup: this.onInputKeyUp\r
2707             }\r
2708         });\r
2709 \r
2710         this.inputItem = new Ext.form.TextField(config); \r
2711         this.menu.add(this.inputItem);\r
2712         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
2713     },\r
2714     \r
2715     /**\r
2716      * @private\r
2717      * Template method that is to get and return the value of the filter.\r
2718      * @return {String} The value of this filter\r
2719      */\r
2720     getValue : function () {\r
2721         return this.inputItem.getValue();\r
2722     },\r
2723     \r
2724     /**\r
2725      * @private\r
2726      * Template method that is to set the value of the filter.\r
2727      * @param {Object} value The value to set the filter\r
2728      */ \r
2729     setValue : function (value) {\r
2730         this.inputItem.setValue(value);\r
2731         this.fireEvent('update', this);\r
2732     },\r
2733 \r
2734     /**\r
2735      * @private\r
2736      * Template method that is to return <tt>true</tt> if the filter\r
2737      * has enough configuration information to be activated.\r
2738      * @return {Boolean}\r
2739      */\r
2740     isActivatable : function () {\r
2741         return this.inputItem.getValue().length > 0;\r
2742     },\r
2743 \r
2744     /**\r
2745      * @private\r
2746      * Template method that is to get and return serialized filter data for\r
2747      * transmission to the server.\r
2748      * @return {Object/Array} An object or collection of objects containing\r
2749      * key value pairs representing the current configuration of the filter.\r
2750      */\r
2751     getSerialArgs : function () {\r
2752         return {type: 'string', value: this.getValue()};\r
2753     },\r
2754 \r
2755     /**\r
2756      * Template method that is to validate the provided Ext.data.Record\r
2757      * against the filters configuration.\r
2758      * @param {Ext.data.Record} record The record to validate\r
2759      * @return {Boolean} true if the record is valid within the bounds\r
2760      * of the filter, false otherwise.\r
2761      */\r
2762     validateRecord : function (record) {\r
2763         var val = record.get(this.dataIndex);\r
2764 \r
2765         if(typeof val != 'string') {\r
2766             return (this.getValue().length === 0);\r
2767         }\r
2768 \r
2769         return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;\r
2770     },\r
2771     \r
2772     /**  \r
2773      * @private\r
2774      * Handler method called when there is a keyup event on this.inputItem\r
2775      */\r
2776     onInputKeyUp : function (field, e) {\r
2777         var k = e.getKey();\r
2778         if (k == e.RETURN && field.isValid()) {\r
2779             e.stopEvent();\r
2780             this.menu.hide(true);\r
2781             return;\r
2782         }\r
2783         // restart the timer\r
2784         this.updateTask.delay(this.updateBuffer);\r
2785     }\r
2786 });\r
2787 Ext.namespace('Ext.ux.menu');\r
2788 \r
2789 /** \r
2790  * @class Ext.ux.menu.ListMenu\r
2791  * @extends Ext.menu.Menu\r
2792  * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.\r
2793  * Although not listed as configuration options for this class, this class\r
2794  * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.\r
2795  */\r
2796 Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {\r
2797     /**\r
2798      * @cfg {String} labelField\r
2799      * Defaults to 'text'.\r
2800      */\r
2801     labelField :  'text',\r
2802     /**\r
2803      * @cfg {String} paramPrefix\r
2804      * Defaults to 'Loading...'.\r
2805      */\r
2806     loadingText : 'Loading...',\r
2807     /**\r
2808      * @cfg {Boolean} loadOnShow\r
2809      * Defaults to true.\r
2810      */\r
2811     loadOnShow : true,\r
2812     /**\r
2813      * @cfg {Boolean} single\r
2814      * Specify true to group all items in this list into a single-select\r
2815      * radio button group. Defaults to false.\r
2816      */\r
2817     single : false,\r
2818 \r
2819     constructor : function (cfg) {\r
2820         this.selected = [];\r
2821         this.addEvents(\r
2822             /**\r
2823              * @event checkchange\r
2824              * Fires when there is a change in checked items from this list\r
2825              * @param {Object} item Ext.menu.CheckItem\r
2826              * @param {Object} checked The checked value that was set\r
2827              */\r
2828             'checkchange'\r
2829         );\r
2830       \r
2831         Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {});\r
2832     \r
2833         if(!cfg.store && cfg.options){\r
2834             var options = [];\r
2835             for(var i=0, len=cfg.options.length; i<len; i++){\r
2836                 var value = cfg.options[i];\r
2837                 switch(Ext.type(value)){\r
2838                     case 'array':  options.push(value); break;\r
2839                     case 'object': options.push([value.id, value[this.labelField]]); break;\r
2840                     case 'string': options.push([value, value]); break;\r
2841                 }\r
2842             }\r
2843             \r
2844             this.store = new Ext.data.Store({\r
2845                 reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField]),\r
2846                 data:   options,\r
2847                 listeners: {\r
2848                     'load': this.onLoad,\r
2849                     scope:  this\r
2850                 }\r
2851             });\r
2852             this.loaded = true;\r
2853         } else {\r
2854             this.add({text: this.loadingText, iconCls: 'loading-indicator'});\r
2855             this.store.on('load', this.onLoad, this);\r
2856         }\r
2857     },\r
2858 \r
2859     destroy : function () {\r
2860         if (this.store) {\r
2861             this.store.destroy();    \r
2862         }\r
2863         Ext.ux.menu.ListMenu.superclass.destroy.call(this);\r
2864     },\r
2865 \r
2866     /**\r
2867      * Lists will initially show a 'loading' item while the data is retrieved from the store.\r
2868      * In some cases the loaded data will result in a list that goes off the screen to the\r
2869      * right (as placement calculations were done with the loading item). This adapter will\r
2870      * allow show to be called with no arguments to show with the previous arguments and\r
2871      * thus recalculate the width and potentially hang the menu from the left.\r
2872      */\r
2873     show : function () {\r
2874         var lastArgs = null;\r
2875         return function(){\r
2876             if(arguments.length === 0){\r
2877                 Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs);\r
2878             } else {\r
2879                 lastArgs = arguments;\r
2880                 if (this.loadOnShow && !this.loaded) {\r
2881                     this.store.load();\r
2882                 }\r
2883                 Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments);\r
2884             }\r
2885         };\r
2886     }(),\r
2887     \r
2888     /** @private */\r
2889     onLoad : function (store, records) {\r
2890         var visible = this.isVisible();\r
2891         this.hide(false);\r
2892         \r
2893         this.removeAll(true);\r
2894         \r
2895         var gid = this.single ? Ext.id() : null;\r
2896         for(var i=0, len=records.length; i<len; i++){\r
2897             var item = new Ext.menu.CheckItem({\r
2898                 text:    records[i].get(this.labelField), \r
2899                 group:   gid,\r
2900                 checked: this.selected.indexOf(records[i].id) > -1,\r
2901                 hideOnClick: false});\r
2902             \r
2903             item.itemId = records[i].id;\r
2904             item.on('checkchange', this.checkChange, this);\r
2905                         \r
2906             this.add(item);\r
2907         }\r
2908         \r
2909         this.loaded = true;\r
2910         \r
2911         if (visible) {\r
2912             this.show();\r
2913         }       \r
2914         this.fireEvent('load', this, records);\r
2915     },\r
2916 \r
2917     /**\r
2918      * Get the selected items.\r
2919      * @return {Array} selected\r
2920      */\r
2921     getSelected : function () {\r
2922         return this.selected;\r
2923     },\r
2924     \r
2925     /** @private */\r
2926     setSelected : function (value) {\r
2927         value = this.selected = [].concat(value);\r
2928 \r
2929         if (this.loaded) {\r
2930             this.items.each(function(item){\r
2931                 item.setChecked(false, true);\r
2932                 for (var i = 0, len = value.length; i < len; i++) {\r
2933                     if (item.itemId == value[i]) {\r
2934                         item.setChecked(true, true);\r
2935                     }\r
2936                 }\r
2937             }, this);\r
2938         }\r
2939     },\r
2940     \r
2941     /**\r
2942      * Handler for the 'checkchange' event from an check item in this menu\r
2943      * @param {Object} item Ext.menu.CheckItem\r
2944      * @param {Object} checked The checked value that was set\r
2945      */\r
2946     checkChange : function (item, checked) {\r
2947         var value = [];\r
2948         this.items.each(function(item){\r
2949             if (item.checked) {\r
2950                 value.push(item.itemId);\r
2951             }\r
2952         },this);\r
2953         this.selected = value;\r
2954         \r
2955         this.fireEvent('checkchange', item, checked);\r
2956     }    \r
2957 });Ext.ns('Ext.ux.menu');\r
2958 \r
2959 /** \r
2960  * @class Ext.ux.menu.RangeMenu\r
2961  * @extends Ext.menu.Menu\r
2962  * Custom implementation of Ext.menu.Menu that has preconfigured\r
2963  * items for gt, lt, eq.\r
2964  * <p><b><u>Example Usage:</u></b></p>\r
2965  * <pre><code>    \r
2966 \r
2967  * </code></pre> \r
2968  */\r
2969 Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, {\r
2970 \r
2971     constructor : function (config) {\r
2972 \r
2973         Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config);\r
2974 \r
2975         this.addEvents(\r
2976             /**\r
2977              * @event update\r
2978              * Fires when a filter configuration has changed\r
2979              * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
2980              */\r
2981             'update'\r
2982         );\r
2983       \r
2984         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
2985     \r
2986         var i, len, item, cfg, Cls;\r
2987 \r
2988         for (i = 0, len = this.menuItems.length; i < len; i++) {\r
2989             item = this.menuItems[i];\r
2990             if (item !== '-') {\r
2991                 // defaults\r
2992                 cfg = {\r
2993                     itemId: 'range-' + item,\r
2994                     enableKeyEvents: true,\r
2995                     iconCls: this.iconCls[item] || 'no-icon',\r
2996                     listeners: {\r
2997                         scope: this,\r
2998                         keyup: this.onInputKeyUp\r
2999                     }\r
3000                 };\r
3001                 Ext.apply(\r
3002                     cfg,\r
3003                     // custom configs\r
3004                     Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]),\r
3005                     // configurable defaults\r
3006                     this.menuItemCfgs\r
3007                 );\r
3008                 Cls = cfg.fieldCls || this.fieldCls;\r
3009                 item = this.fields[item] = new Cls(cfg);\r
3010             }\r
3011             this.add(item);\r
3012         }\r
3013     },\r
3014 \r
3015     /**\r
3016      * @private\r
3017      * called by this.updateTask\r
3018      */\r
3019     fireUpdate : function () {\r
3020         this.fireEvent('update', this);\r
3021     },\r
3022     \r
3023     /**\r
3024      * Get and return the value of the filter.\r
3025      * @return {String} The value of this filter\r
3026      */\r
3027     getValue : function () {\r
3028         var result = {}, key, field;\r
3029         for (key in this.fields) {\r
3030             field = this.fields[key];\r
3031             if (field.isValid() && String(field.getValue()).length > 0) {\r
3032                 result[key] = field.getValue();\r
3033             }\r
3034         }\r
3035         return result;\r
3036     },\r
3037   \r
3038     /**\r
3039      * Set the value of this menu and fires the 'update' event.\r
3040      * @param {Object} data The data to assign to this menu\r
3041      */ \r
3042     setValue : function (data) {\r
3043         var key;\r
3044         for (key in this.fields) {\r
3045             this.fields[key].setValue(data[key] !== undefined ? data[key] : '');\r
3046         }\r
3047         this.fireEvent('update', this);\r
3048     },\r
3049 \r
3050     /**  \r
3051      * @private\r
3052      * Handler method called when there is a keyup event on an input\r
3053      * item of this menu.\r
3054      */\r
3055     onInputKeyUp : function (field, e) {\r
3056         var k = e.getKey();\r
3057         if (k == e.RETURN && field.isValid()) {\r
3058             e.stopEvent();\r
3059             this.hide(true);\r
3060             return;\r
3061         }\r
3062         \r
3063         if (field == this.fields.eq) {\r
3064             if (this.fields.gt) {\r
3065                 this.fields.gt.setValue(null);\r
3066             }\r
3067             if (this.fields.lt) {\r
3068                 this.fields.lt.setValue(null);\r
3069             }\r
3070         }\r
3071         else {\r
3072             this.fields.eq.setValue(null);\r
3073         }\r
3074         \r
3075         // restart the timer\r
3076         this.updateTask.delay(this.updateBuffer);\r
3077     }\r
3078 });\r
3079 Ext.ns('Ext.ux.grid');\r
3080 \r
3081 /**\r
3082  * @class Ext.ux.grid.GroupSummary\r
3083  * @extends Ext.util.Observable\r
3084  * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
3085  * updated grouped summary row.\r
3086  */\r
3087 Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
3088     /**\r
3089      * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
3090 summaryRenderer: function(v, params, data){\r
3091     return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
3092 },\r
3093      * </code></pre>\r
3094      */\r
3095     /**\r
3096      * @cfg {String} summaryType (Optional) The type of\r
3097      * calculation to be used for the column.  For options available see\r
3098      * {@link #Calculations}.\r
3099      */\r
3100 \r
3101     constructor : function(config){\r
3102         Ext.apply(this, config);\r
3103         Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
3104     },\r
3105     init : function(grid){\r
3106         this.grid = grid;\r
3107         var v = this.view = grid.getView();\r
3108         v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
3109 \r
3110         v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
3111         v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
3112         v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
3113         v.afterMethod('onUpdate', this.doUpdate, this);\r
3114         v.afterMethod('onRemove', this.doRemove, this);\r
3115 \r
3116         if(!this.rowTpl){\r
3117             this.rowTpl = new Ext.Template(\r
3118                 '<div class="x-grid3-summary-row" style="{tstyle}">',\r
3119                 '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
3120                     '<tbody><tr>{cells}</tr></tbody>',\r
3121                 '</table></div>'\r
3122             );\r
3123             this.rowTpl.disableFormats = true;\r
3124         }\r
3125         this.rowTpl.compile();\r
3126 \r
3127         if(!this.cellTpl){\r
3128             this.cellTpl = new Ext.Template(\r
3129                 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
3130                 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
3131                 "</td>"\r
3132             );\r
3133             this.cellTpl.disableFormats = true;\r
3134         }\r
3135         this.cellTpl.compile();\r
3136     },\r
3137 \r
3138     /**\r
3139      * Toggle the display of the summary row on/off\r
3140      * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
3141      */\r
3142     toggleSummaries : function(visible){\r
3143         var el = this.grid.getGridEl();\r
3144         if(el){\r
3145             if(visible === undefined){\r
3146                 visible = el.hasClass('x-grid-hide-summary');\r
3147             }\r
3148             el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
3149         }\r
3150     },\r
3151 \r
3152     renderSummary : function(o, cs){\r
3153         cs = cs || this.view.getColumnData();\r
3154         var cfg = this.grid.getColumnModel().config,\r
3155             buf = [], c, p = {}, cf, last = cs.length-1;\r
3156         for(var i = 0, len = cs.length; i < len; i++){\r
3157             c = cs[i];\r
3158             cf = cfg[i];\r
3159             p.id = c.id;\r
3160             p.style = c.style;\r
3161             p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
3162             if(cf.summaryType || cf.summaryRenderer){\r
3163                 p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
3164             }else{\r
3165                 p.value = '';\r
3166             }\r
3167             if(p.value == undefined || p.value === "") p.value = "&#160;";\r
3168             buf[buf.length] = this.cellTpl.apply(p);\r
3169         }\r
3170 \r
3171         return this.rowTpl.apply({\r
3172             tstyle: 'width:'+this.view.getTotalWidth()+';',\r
3173             cells: buf.join('')\r
3174         });\r
3175     },\r
3176 \r
3177     /**\r
3178      * @private\r
3179      * @param {Object} rs\r
3180      * @param {Object} cs\r
3181      */\r
3182     calculate : function(rs, cs){\r
3183         var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;\r
3184         for(var j = 0, jlen = rs.length; j < jlen; j++){\r
3185             r = rs[j];\r
3186             for(var i = 0, len = cs.length; i < len; i++){\r
3187                 c = cs[i];\r
3188                 cf = cfg[i];\r
3189                 if(cf.summaryType){\r
3190                     data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
3191                 }\r
3192             }\r
3193         }\r
3194         return data;\r
3195     },\r
3196 \r
3197     doGroupEnd : function(buf, g, cs, ds, colCount){\r
3198         var data = this.calculate(g.rs, cs);\r
3199         buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
3200     },\r
3201 \r
3202     doWidth : function(col, w, tw){\r
3203         var gs = this.view.getGroups(), s;\r
3204         for(var i = 0, len = gs.length; i < len; i++){\r
3205             s = gs[i].childNodes[2];\r
3206             s.style.width = tw;\r
3207             s.firstChild.style.width = tw;\r
3208             s.firstChild.rows[0].childNodes[col].style.width = w;\r
3209         }\r
3210     },\r
3211 \r
3212     doAllWidths : function(ws, tw){\r
3213         var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
3214         for(var i = 0, len = gs.length; i < len; i++){\r
3215             s = gs[i].childNodes[2];\r
3216             s.style.width = tw;\r
3217             s.firstChild.style.width = tw;\r
3218             cells = s.firstChild.rows[0].childNodes;\r
3219             for(var j = 0; j < wlen; j++){\r
3220                 cells[j].style.width = ws[j];\r
3221             }\r
3222         }\r
3223     },\r
3224 \r
3225     doHidden : function(col, hidden, tw){\r
3226         var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
3227         for(var i = 0, len = gs.length; i < len; i++){\r
3228             s = gs[i].childNodes[2];\r
3229             s.style.width = tw;\r
3230             s.firstChild.style.width = tw;\r
3231             s.firstChild.rows[0].childNodes[col].style.display = display;\r
3232         }\r
3233     },\r
3234 \r
3235     // Note: requires that all (or the first) record in the\r
3236     // group share the same group value. Returns false if the group\r
3237     // could not be found.\r
3238     refreshSummary : function(groupValue){\r
3239         return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
3240     },\r
3241 \r
3242     getSummaryNode : function(gid){\r
3243         var g = Ext.fly(gid, '_gsummary');\r
3244         if(g){\r
3245             return g.down('.x-grid3-summary-row', true);\r
3246         }\r
3247         return null;\r
3248     },\r
3249 \r
3250     refreshSummaryById : function(gid){\r
3251         var g = Ext.getDom(gid);\r
3252         if(!g){\r
3253             return false;\r
3254         }\r
3255         var rs = [];\r
3256         this.grid.getStore().each(function(r){\r
3257             if(r._groupId == gid){\r
3258                 rs[rs.length] = r;\r
3259             }\r
3260         });\r
3261         var cs = this.view.getColumnData(),\r
3262             data = this.calculate(rs, cs),\r
3263             markup = this.renderSummary({data: data}, cs),\r
3264             existing = this.getSummaryNode(gid);\r
3265             \r
3266         if(existing){\r
3267             g.removeChild(existing);\r
3268         }\r
3269         Ext.DomHelper.append(g, markup);\r
3270         return true;\r
3271     },\r
3272 \r
3273     doUpdate : function(ds, record){\r
3274         this.refreshSummaryById(record._groupId);\r
3275     },\r
3276 \r
3277     doRemove : function(ds, record, index, isUpdate){\r
3278         if(!isUpdate){\r
3279             this.refreshSummaryById(record._groupId);\r
3280         }\r
3281     },\r
3282 \r
3283     /**\r
3284      * Show a message in the summary row.\r
3285      * <pre><code>\r
3286 grid.on('afteredit', function(){\r
3287     var groupValue = 'Ext Forms: Field Anchoring';\r
3288     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
3289 });\r
3290      * </code></pre>\r
3291      * @param {String} groupValue\r
3292      * @param {String} msg Text to use as innerHTML for the summary row.\r
3293      */\r
3294     showSummaryMsg : function(groupValue, msg){\r
3295         var gid = this.view.getGroupId(groupValue),\r
3296              node = this.getSummaryNode(gid);\r
3297         if(node){\r
3298             node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
3299         }\r
3300     }\r
3301 });\r
3302 \r
3303 //backwards compat\r
3304 Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
3305 \r
3306 \r
3307 /**\r
3308  * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
3309  * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
3310  * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
3311  * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
3312  * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
3313  * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
3314  * </ul></div>\r
3315  * <p>Custom calculations may be implemented.  An example of\r
3316  * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
3317 // define a custom summary function\r
3318 Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
3319     return v + (record.data.estimate * record.data.rate);\r
3320 };\r
3321  * </code></pre>\r
3322  * @property Calculations\r
3323  */\r
3324 \r
3325 Ext.ux.grid.GroupSummary.Calculations = {\r
3326     'sum' : function(v, record, field){\r
3327         return v + (record.data[field]||0);\r
3328     },\r
3329 \r
3330     'count' : function(v, record, field, data){\r
3331         return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
3332     },\r
3333 \r
3334     'max' : function(v, record, field, data){\r
3335         var v = record.data[field];\r
3336         var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
3337         return v > max ? (data[field+'max'] = v) : max;\r
3338     },\r
3339 \r
3340     'min' : function(v, record, field, data){\r
3341         var v = record.data[field];\r
3342         var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
3343         return v < min ? (data[field+'min'] = v) : min;\r
3344     },\r
3345 \r
3346     'average' : function(v, record, field, data){\r
3347         var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
3348         var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
3349         return t === 0 ? 0 : t / c;\r
3350     }\r
3351 };\r
3352 Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
3353 \r
3354 /**\r
3355  * @class Ext.ux.grid.HybridSummary\r
3356  * @extends Ext.ux.grid.GroupSummary\r
3357  * Adds capability to specify the summary data for the group via json as illustrated here:\r
3358  * <pre><code>\r
3359 {\r
3360     data: [\r
3361         {\r
3362             projectId: 100,     project: 'House',\r
3363             taskId:    112, description: 'Paint',\r
3364             estimate:    6,        rate:     150,\r
3365             due:'06/24/2007'\r
3366         },\r
3367         ...\r
3368     ],\r
3369 \r
3370     summaryData: {\r
3371         'House': {\r
3372             description: 14, estimate: 9,\r
3373                    rate: 99, due: new Date(2009, 6, 29),\r
3374                    cost: 999\r
3375         }\r
3376     }\r
3377 }\r
3378  * </code></pre>\r
3379  *\r
3380  */\r
3381 Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {\r
3382     /**\r
3383      * @private\r
3384      * @param {Object} rs\r
3385      * @param {Object} cs\r
3386      */\r
3387     calculate : function(rs, cs){\r
3388         var gcol = this.view.getGroupField(),\r
3389             gvalue = rs[0].data[gcol],\r
3390             gdata = this.getSummaryData(gvalue);\r
3391         return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);\r
3392     },\r
3393 \r
3394     /**\r
3395      * <pre><code>\r
3396 grid.on('afteredit', function(){\r
3397     var groupValue = 'Ext Forms: Field Anchoring';\r
3398     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
3399     setTimeout(function(){ // simulate server call\r
3400         // HybridSummary class implements updateSummaryData\r
3401         summary.updateSummaryData(groupValue,\r
3402             // create data object based on configured dataIndex\r
3403             {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});\r
3404     }, 2000);\r
3405 });\r
3406      * </code></pre>\r
3407      * @param {String} groupValue\r
3408      * @param {Object} data data object\r
3409      * @param {Boolean} skipRefresh (Optional) Defaults to false\r
3410      */\r
3411     updateSummaryData : function(groupValue, data, skipRefresh){\r
3412         var json = this.grid.getStore().reader.jsonData;\r
3413         if(!json.summaryData){\r
3414             json.summaryData = {};\r
3415         }\r
3416         json.summaryData[groupValue] = data;\r
3417         if(!skipRefresh){\r
3418             this.refreshSummary(groupValue);\r
3419         }\r
3420     },\r
3421 \r
3422     /**\r
3423      * Returns the summaryData for the specified groupValue or null.\r
3424      * @param {String} groupValue\r
3425      * @return {Object} summaryData\r
3426      */\r
3427     getSummaryData : function(groupValue){\r
3428         var json = this.grid.getStore().reader.jsonData;\r
3429         if(json && json.summaryData){\r
3430             return json.summaryData[groupValue];\r
3431         }\r
3432         return null;\r
3433     }\r
3434 });\r
3435 \r
3436 //backwards compat\r
3437 Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;\r
3438 Ext.ux.GroupTab = Ext.extend(Ext.Container, {\r
3439     mainItem: 0,\r
3440     \r
3441     expanded: true,\r
3442     \r
3443     deferredRender: true,\r
3444     \r
3445     activeTab: null,\r
3446     \r
3447     idDelimiter: '__',\r
3448     \r
3449     headerAsText: false,\r
3450     \r
3451     frame: false,\r
3452     \r
3453     hideBorders: true,\r
3454     \r
3455     initComponent: function(config){\r
3456         Ext.apply(this, config);\r
3457         this.frame = false;\r
3458         \r
3459         Ext.ux.GroupTab.superclass.initComponent.call(this);\r
3460         \r
3461         this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');\r
3462         \r
3463         this.setLayout(new Ext.layout.CardLayout({\r
3464             deferredRender: this.deferredRender\r
3465         }));\r
3466         \r
3467         if (!this.stack) {\r
3468             this.stack = Ext.TabPanel.AccessStack();\r
3469         }\r
3470         \r
3471         this.initItems();\r
3472         \r
3473         this.on('beforerender', function(){\r
3474             this.groupEl = this.ownerCt.getGroupEl(this);\r
3475         }, this);\r
3476         \r
3477         this.on('add', this.onAdd, this, {\r
3478             target: this\r
3479         });\r
3480         this.on('remove', this.onRemove, this, {\r
3481             target: this\r
3482         });\r
3483         \r
3484         if (this.mainItem !== undefined) {\r
3485             var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);\r
3486             delete this.mainItem;\r
3487             this.setMainItem(item);\r
3488         }\r
3489     },\r
3490     \r
3491     /**\r
3492      * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which\r
3493      * can return false to cancel the tab change.\r
3494      * @param {String/Panel} tab The id or tab Panel to activate\r
3495      */\r
3496     setActiveTab : function(item){\r
3497         item = this.getComponent(item);\r
3498         if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){\r
3499             return;\r
3500         }\r
3501         if(!this.rendered){\r
3502             this.activeTab = item;\r
3503             return;\r
3504         }\r
3505         if(this.activeTab != item){\r
3506             if(this.activeTab && this.activeTab != this.mainItem){\r
3507                 var oldEl = this.getTabEl(this.activeTab);\r
3508                 if(oldEl){\r
3509                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
3510                 }\r
3511                 this.activeTab.fireEvent('deactivate', this.activeTab);\r
3512             }\r
3513             var el = this.getTabEl(item);\r
3514             Ext.fly(el).addClass('x-grouptabs-strip-active');\r
3515             this.activeTab = item;\r
3516             this.stack.add(item);\r
3517 \r
3518             this.layout.setActiveItem(item);\r
3519             if(this.layoutOnTabChange && item.doLayout){\r
3520                 item.doLayout();\r
3521             }\r
3522             if(this.scrolling){\r
3523                 this.scrollToTab(item, this.animScroll);\r
3524             }\r
3525 \r
3526             item.fireEvent('activate', item);\r
3527             this.fireEvent('tabchange', this, item);\r
3528         }\r
3529     },\r
3530     \r
3531     getTabEl: function(item){\r
3532         if (item == this.mainItem) {\r
3533             return this.groupEl;\r
3534         }\r
3535         return Ext.TabPanel.prototype.getTabEl.call(this, item);\r
3536     },\r
3537     \r
3538     onRender: function(ct, position){\r
3539         Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);\r
3540         \r
3541         this.strip = Ext.fly(this.groupEl).createChild({\r
3542             tag: 'ul',\r
3543             cls: 'x-grouptabs-sub'\r
3544         });\r
3545 \r
3546         this.tooltip = new Ext.ToolTip({\r
3547            target: this.groupEl,\r
3548            delegate: 'a.x-grouptabs-text',\r
3549            trackMouse: true,\r
3550            renderTo: document.body,\r
3551            listeners: {\r
3552                beforeshow: function(tip) {\r
3553                    var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)\r
3554                        ? this.mainItem\r
3555                        : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);\r
3556 \r
3557                    if(!item.tabTip) {\r
3558                        return false;\r
3559                    }\r
3560                    tip.body.dom.innerHTML = item.tabTip;\r
3561                },\r
3562                scope: this\r
3563            }\r
3564         });\r
3565                 \r
3566         if (!this.itemTpl) {\r
3567             var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');\r
3568             tt.disableFormats = true;\r
3569             tt.compile();\r
3570             Ext.ux.GroupTab.prototype.itemTpl = tt;\r
3571         }\r
3572         \r
3573         this.items.each(this.initTab, this);\r
3574     },\r
3575     \r
3576     afterRender: function(){\r
3577         Ext.ux.GroupTab.superclass.afterRender.call(this);\r
3578         \r
3579         if (this.activeTab !== undefined) {\r
3580             var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);\r
3581             delete this.activeTab;\r
3582             this.setActiveTab(item);\r
3583         }\r
3584     },\r
3585     \r
3586     // private\r
3587     initTab: function(item, index){\r
3588         var before = this.strip.dom.childNodes[index];\r
3589         var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);\r
3590         \r
3591         if (item === this.mainItem) {\r
3592             item.tabEl = this.groupEl;\r
3593             p.cls += ' x-grouptabs-main-item';\r
3594         }\r
3595         \r
3596         var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
3597         \r
3598         item.tabEl = item.tabEl || el;\r
3599                 \r
3600         item.on('disable', this.onItemDisabled, this);\r
3601         item.on('enable', this.onItemEnabled, this);\r
3602         item.on('titlechange', this.onItemTitleChanged, this);\r
3603         item.on('iconchange', this.onItemIconChanged, this);\r
3604         item.on('beforeshow', this.onBeforeShowItem, this);\r
3605     },\r
3606     \r
3607     setMainItem: function(item){\r
3608         item = this.getComponent(item);\r
3609         if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {\r
3610             return;\r
3611         }\r
3612         \r
3613         this.mainItem = item;\r
3614     },\r
3615     \r
3616     getMainItem: function(){\r
3617         return this.mainItem || null;\r
3618     },\r
3619     \r
3620     // private\r
3621     onBeforeShowItem: function(item){\r
3622         if (item != this.activeTab) {\r
3623             this.setActiveTab(item);\r
3624             return false;\r
3625         }\r
3626     },\r
3627     \r
3628     // private\r
3629     onAdd: function(gt, item, index){\r
3630         if (this.rendered) {\r
3631             this.initTab.call(this, item, index);\r
3632         }\r
3633     },\r
3634     \r
3635     // private\r
3636     onRemove: function(tp, item){\r
3637         Ext.destroy(Ext.get(this.getTabEl(item)));\r
3638         this.stack.remove(item);\r
3639         item.un('disable', this.onItemDisabled, this);\r
3640         item.un('enable', this.onItemEnabled, this);\r
3641         item.un('titlechange', this.onItemTitleChanged, this);\r
3642         item.un('iconchange', this.onItemIconChanged, this);\r
3643         item.un('beforeshow', this.onBeforeShowItem, this);\r
3644         if (item == this.activeTab) {\r
3645             var next = this.stack.next();\r
3646             if (next) {\r
3647                 this.setActiveTab(next);\r
3648             }\r
3649             else if (this.items.getCount() > 0) {\r
3650                 this.setActiveTab(0);\r
3651             }\r
3652             else {\r
3653                 this.activeTab = null;\r
3654             }\r
3655         }\r
3656     },\r
3657     \r
3658     // private\r
3659     onBeforeAdd: function(item){\r
3660         var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);\r
3661         if (existing) {\r
3662             this.setActiveTab(item);\r
3663             return false;\r
3664         }\r
3665         Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);\r
3666         var es = item.elements;\r
3667         item.elements = es ? es.replace(',header', '') : es;\r
3668         item.border = (item.border === true);\r
3669     },\r
3670     \r
3671     // private\r
3672     onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,\r
3673     onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,\r
3674     \r
3675     // private\r
3676     onItemTitleChanged: function(item){\r
3677         var el = this.getTabEl(item);\r
3678         if (el) {\r
3679             Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;\r
3680         }\r
3681     },\r
3682     \r
3683     //private\r
3684     onItemIconChanged: function(item, iconCls, oldCls){\r
3685         var el = this.getTabEl(item);\r
3686         if (el) {\r
3687             Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);\r
3688         }\r
3689     },\r
3690     \r
3691     beforeDestroy: function(){\r
3692         Ext.TabPanel.prototype.beforeDestroy.call(this);\r
3693         this.tooltip.destroy();\r
3694     }\r
3695 });\r
3696 \r
3697 Ext.reg('grouptab', Ext.ux.GroupTab);\r
3698 Ext.ns('Ext.ux');\r
3699 \r
3700 Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {\r
3701     tabPosition: 'left',\r
3702     \r
3703     alternateColor: false,\r
3704     \r
3705     alternateCls: 'x-grouptabs-panel-alt',\r
3706     \r
3707     defaultType: 'grouptab',\r
3708     \r
3709     deferredRender: false,\r
3710     \r
3711     activeGroup : null,\r
3712     \r
3713     initComponent: function(){\r
3714         Ext.ux.GroupTabPanel.superclass.initComponent.call(this);\r
3715         \r
3716         this.addEvents(\r
3717             'beforegroupchange',\r
3718             'groupchange'\r
3719         );\r
3720         this.elements = 'body,header';\r
3721         this.stripTarget = 'header';\r
3722         \r
3723         this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';\r
3724         \r
3725         this.addClass('x-grouptabs-panel');\r
3726         \r
3727         if (this.tabStyle && this.tabStyle != '') {\r
3728             this.addClass('x-grouptabs-panel-' + this.tabStyle);\r
3729         }\r
3730         \r
3731         if (this.alternateColor) {\r
3732             this.addClass(this.alternateCls);\r
3733         }\r
3734         \r
3735         this.on('beforeadd', function(gtp, item, index){\r
3736             this.initGroup(item, index);\r
3737         });          \r
3738     },\r
3739     \r
3740     initEvents : function() {\r
3741         this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);\r
3742     },\r
3743         \r
3744     onRender: function(ct, position){\r
3745         Ext.TabPanel.superclass.onRender.call(this, ct, position);\r
3746         if(this.plain){\r
3747             var pos = this.tabPosition == 'top' ? 'header' : 'footer';\r
3748             this[pos].addClass('x-tab-panel-'+pos+'-plain');\r
3749         }\r
3750 \r
3751         var st = this[this.stripTarget];\r
3752 \r
3753         this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{\r
3754             tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});\r
3755 \r
3756         var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);\r
3757         this.strip = new Ext.Element(this.stripWrap.dom.firstChild);\r
3758 \r
3759         this.header.addClass('x-grouptabs-panel-header');\r
3760         this.bwrap.addClass('x-grouptabs-bwrap');\r
3761         this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');\r
3762 \r
3763         if (!this.groupTpl) {\r
3764             var tt = new Ext.Template(\r
3765                 '<li class="{cls}" id="{id}">', \r
3766                 '<a class="x-grouptabs-expand" onclick="return false;"></a>', \r
3767                 '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',\r
3768                 '<span>{text}</span></a>', \r
3769                 '</li>'\r
3770             );\r
3771             tt.disableFormats = true;\r
3772             tt.compile();\r
3773             Ext.ux.GroupTabPanel.prototype.groupTpl = tt;\r
3774         }\r
3775         this.items.each(this.initGroup, this);\r
3776     },\r
3777     \r
3778     afterRender: function(){\r
3779         Ext.ux.GroupTabPanel.superclass.afterRender.call(this);\r
3780         \r
3781         this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({\r
3782             cls: 'x-tab-joint'\r
3783         });\r
3784         \r
3785         this.addClass('x-tab-panel-' + this.tabPosition);\r
3786         this.header.setWidth(this.tabWidth);\r
3787         \r
3788         if (this.activeGroup !== undefined) {\r
3789             var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);\r
3790             delete this.activeGroup;\r
3791             this.setActiveGroup(group);\r
3792             group.setActiveTab(group.getMainItem());\r
3793         }\r
3794     },\r
3795 \r
3796     getGroupEl : Ext.TabPanel.prototype.getTabEl,\r
3797         \r
3798     // private\r
3799     findTargets: function(e){\r
3800         var item = null,\r
3801             itemEl = e.getTarget('li', this.strip);\r
3802         if (itemEl) {\r
3803             item = this.findById(itemEl.id.split(this.idDelimiter)[1]);\r
3804             if (item.disabled) {\r
3805                 return {\r
3806                     expand: null,\r
3807                     item: null,\r
3808                     el: null\r
3809                 };\r
3810             }\r
3811         }\r
3812         return {\r
3813             expand: e.getTarget('.x-grouptabs-expand', this.strip),\r
3814             isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),\r
3815             item: item,\r
3816             el: itemEl\r
3817         };\r
3818     },\r
3819     \r
3820     // private\r
3821     onStripMouseDown: function(e){\r
3822         if (e.button != 0) {\r
3823             return;\r
3824         }\r
3825         e.preventDefault();\r
3826         var t = this.findTargets(e);\r
3827         if (t.expand) {\r
3828             this.toggleGroup(t.el);\r
3829         }\r
3830         else if (t.item) {\r
3831             if(t.isGroup) {\r
3832                 t.item.setActiveTab(t.item.getMainItem());\r
3833             }\r
3834             else {\r
3835                 t.item.ownerCt.setActiveTab(t.item);\r
3836             }\r
3837         }\r
3838     },\r
3839     \r
3840     expandGroup: function(groupEl){\r
3841         if(groupEl.isXType) {\r
3842             groupEl = this.getGroupEl(groupEl);\r
3843         }\r
3844         Ext.fly(groupEl).addClass('x-grouptabs-expanded');\r
3845     },\r
3846     \r
3847     toggleGroup: function(groupEl){\r
3848         if(groupEl.isXType) {\r
3849             groupEl = this.getGroupEl(groupEl);\r
3850         }        \r
3851         Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');\r
3852         this.syncTabJoint();\r
3853     },    \r
3854     \r
3855     syncTabJoint: function(groupEl){\r
3856         if (!this.tabJoint) {\r
3857             return;\r
3858         }\r
3859         \r
3860         groupEl = groupEl || this.getGroupEl(this.activeGroup);\r
3861         if(groupEl) {\r
3862             this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); \r
3863             \r
3864             var y = Ext.isGecko2 ? 0 : 1;\r
3865             if (this.tabPosition == 'left'){\r
3866                 this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);\r
3867             }\r
3868             else {\r
3869                 this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);\r
3870             }           \r
3871         }\r
3872         else {\r
3873             this.tabJoint.hide();\r
3874         }\r
3875     },\r
3876     \r
3877     getActiveTab : function() {\r
3878         if(!this.activeGroup) return null;\r
3879         return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null;  \r
3880     },\r
3881     \r
3882     onResize: function(){\r
3883         Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);\r
3884         this.syncTabJoint();\r
3885     },\r
3886     \r
3887     createCorner: function(el, pos){\r
3888         return Ext.fly(el).createChild({\r
3889             cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos\r
3890         });\r
3891     },\r
3892     \r
3893     initGroup: function(group, index){\r
3894         var before = this.strip.dom.childNodes[index],   \r
3895             p = this.getTemplateArgs(group);\r
3896         if (index === 0) {\r
3897             p.cls += ' x-tab-first';\r
3898         }\r
3899         p.cls += ' x-grouptabs-main';\r
3900         p.text = group.getMainItem().title;\r
3901         \r
3902         var el = before ? this.groupTpl.insertBefore(before, p) : this.groupTpl.append(this.strip, p),\r
3903             tl = this.createCorner(el, 'top-' + this.tabPosition),\r
3904             bl = this.createCorner(el, 'bottom-' + this.tabPosition);\r
3905 \r
3906         if (group.expanded) {\r
3907             this.expandGroup(el);\r
3908         }\r
3909 \r
3910         if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){\r
3911             bl.setLeft('-10px');\r
3912             bl.setBottom('-5px');\r
3913             tl.setLeft('-10px');\r
3914             tl.setTop('-5px');\r
3915         }\r
3916 \r
3917         this.mon(group, {\r
3918             scope: this,\r
3919             changemainitem: this.onGroupChangeMainItem,\r
3920             beforetabchange: this.onGroupBeforeTabChange\r
3921         });\r
3922     },\r
3923     \r
3924     setActiveGroup : function(group) {\r
3925         group = this.getComponent(group);\r
3926         if(!group || this.fireEvent('beforegroupchange', this, group, this.activeGroup) === false){\r
3927             return;\r
3928         }\r
3929         if(!this.rendered){\r
3930             this.activeGroup = group;\r
3931             return;\r
3932         }\r
3933         if(this.activeGroup != group){\r
3934             if(this.activeGroup){\r
3935                 var oldEl = this.getGroupEl(this.activeGroup);\r
3936                 if(oldEl){\r
3937                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
3938                 }\r
3939                 this.activeGroup.fireEvent('deactivate', this.activeGroup);\r
3940             }\r
3941 \r
3942             var groupEl = this.getGroupEl(group);\r
3943             Ext.fly(groupEl).addClass('x-grouptabs-strip-active');\r
3944                         \r
3945             this.activeGroup = group;\r
3946             this.stack.add(group);\r
3947 \r
3948             this.layout.setActiveItem(group);\r
3949             this.syncTabJoint(groupEl);\r
3950 \r
3951             group.fireEvent('activate', group);\r
3952             this.fireEvent('groupchange', this, group);\r
3953         }        \r
3954     },\r
3955     \r
3956     onGroupBeforeTabChange: function(group, newTab, oldTab){\r
3957         if(group !== this.activeGroup || newTab !== oldTab) {\r
3958             this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');\r
3959         } \r
3960         \r
3961         this.expandGroup(this.getGroupEl(group));\r
3962         this.setActiveGroup(group);\r
3963     },\r
3964     \r
3965     getFrameHeight: function(){\r
3966         var h = this.el.getFrameWidth('tb');\r
3967         h += (this.tbar ? this.tbar.getHeight() : 0) +\r
3968         (this.bbar ? this.bbar.getHeight() : 0);\r
3969         \r
3970         return h;\r
3971     },\r
3972     \r
3973     adjustBodyWidth: function(w){\r
3974         return w - this.tabWidth;\r
3975     }\r
3976 });\r
3977 \r
3978 Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/*\r
3979  * Note that this control will most likely remain as an example, and not as a core Ext form\r
3980  * control.  However, the API will be changing in a future release and so should not yet be\r
3981  * treated as a final, stable API at this time.\r
3982  */\r
3983 \r
3984 /**\r
3985  * @class Ext.ux.form.ItemSelector\r
3986  * @extends Ext.form.Field\r
3987  * A control that allows selection of between two Ext.ux.form.MultiSelect controls.\r
3988  *\r
3989  *  @history\r
3990  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
3991  *\r
3992  * @constructor\r
3993  * Create a new ItemSelector\r
3994  * @param {Object} config Configuration options\r
3995  * @xtype itemselector \r
3996  */\r
3997 Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {\r
3998     hideNavIcons:false,\r
3999     imagePath:"",\r
4000     iconUp:"up2.gif",\r
4001     iconDown:"down2.gif",\r
4002     iconLeft:"left2.gif",\r
4003     iconRight:"right2.gif",\r
4004     iconTop:"top2.gif",\r
4005     iconBottom:"bottom2.gif",\r
4006     drawUpIcon:true,\r
4007     drawDownIcon:true,\r
4008     drawLeftIcon:true,\r
4009     drawRightIcon:true,\r
4010     drawTopIcon:true,\r
4011     drawBotIcon:true,\r
4012     delimiter:',',\r
4013     bodyStyle:null,\r
4014     border:false,\r
4015     defaultAutoCreate:{tag: "div"},\r
4016     /**\r
4017      * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)\r
4018      */\r
4019     multiselects:null,\r
4020 \r
4021     initComponent: function(){\r
4022         Ext.ux.form.ItemSelector.superclass.initComponent.call(this);\r
4023         this.addEvents({\r
4024             'rowdblclick' : true,\r
4025             'change' : true\r
4026         });\r
4027     },\r
4028 \r
4029     onRender: function(ct, position){\r
4030         Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);\r
4031 \r
4032         // Internal default configuration for both multiselects\r
4033         var msConfig = [{\r
4034             legend: 'Available',\r
4035             draggable: true,\r
4036             droppable: true,\r
4037             width: 100,\r
4038             height: 100\r
4039         },{\r
4040             legend: 'Selected',\r
4041             droppable: true,\r
4042             draggable: true,\r
4043             width: 100,\r
4044             height: 100\r
4045         }];\r
4046 \r
4047         this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));\r
4048         this.fromMultiselect.on('dblclick', this.onRowDblClick, this);\r
4049 \r
4050         this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));\r
4051         this.toMultiselect.on('dblclick', this.onRowDblClick, this);\r
4052 \r
4053         var p = new Ext.Panel({\r
4054             bodyStyle:this.bodyStyle,\r
4055             border:this.border,\r
4056             layout:"table",\r
4057             layoutConfig:{columns:3}\r
4058         });\r
4059 \r
4060         p.add(this.fromMultiselect);\r
4061         var icons = new Ext.Panel({header:false});\r
4062         p.add(icons);\r
4063         p.add(this.toMultiselect);\r
4064         p.render(this.el);\r
4065         icons.el.down('.'+icons.bwrapCls).remove();\r
4066 \r
4067         // ICON HELL!!!\r
4068         if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")\r
4069             this.imagePath+="/";\r
4070         this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');\r
4071         this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');\r
4072         this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');\r
4073         this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');\r
4074         this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');\r
4075         this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');\r
4076         var el=icons.getEl();\r
4077         this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});\r
4078         el.createChild({tag: 'br'});\r
4079         this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});\r
4080         el.createChild({tag: 'br'});\r
4081         this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});\r
4082         el.createChild({tag: 'br'});\r
4083         this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});\r
4084         el.createChild({tag: 'br'});\r
4085         this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});\r
4086         el.createChild({tag: 'br'});\r
4087         this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});\r
4088         this.toTopIcon.on('click', this.toTop, this);\r
4089         this.upIcon.on('click', this.up, this);\r
4090         this.downIcon.on('click', this.down, this);\r
4091         this.toBottomIcon.on('click', this.toBottom, this);\r
4092         this.addIcon.on('click', this.fromTo, this);\r
4093         this.removeIcon.on('click', this.toFrom, this);\r
4094         if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }\r
4095         if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }\r
4096         if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }\r
4097         if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }\r
4098         if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }\r
4099         if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }\r
4100 \r
4101         var tb = p.body.first();\r
4102         this.el.setWidth(p.body.first().getWidth());\r
4103         p.body.removeClass();\r
4104 \r
4105         this.hiddenName = this.name;\r
4106         var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};\r
4107         this.hiddenField = this.el.createChild(hiddenTag);\r
4108     },\r
4109     \r
4110     doLayout: function(){\r
4111         if(this.rendered){\r
4112             this.fromMultiselect.fs.doLayout();\r
4113             this.toMultiselect.fs.doLayout();\r
4114         }\r
4115     },\r
4116 \r
4117     afterRender: function(){\r
4118         Ext.ux.form.ItemSelector.superclass.afterRender.call(this);\r
4119 \r
4120         this.toStore = this.toMultiselect.store;\r
4121         this.toStore.on('add', this.valueChanged, this);\r
4122         this.toStore.on('remove', this.valueChanged, this);\r
4123         this.toStore.on('load', this.valueChanged, this);\r
4124         this.valueChanged(this.toStore);\r
4125     },\r
4126 \r
4127     toTop : function() {\r
4128         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4129         var records = [];\r
4130         if (selectionsArray.length > 0) {\r
4131             selectionsArray.sort();\r
4132             for (var i=0; i<selectionsArray.length; i++) {\r
4133                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4134                 records.push(record);\r
4135             }\r
4136             selectionsArray = [];\r
4137             for (var i=records.length-1; i>-1; i--) {\r
4138                 record = records[i];\r
4139                 this.toMultiselect.view.store.remove(record);\r
4140                 this.toMultiselect.view.store.insert(0, record);\r
4141                 selectionsArray.push(((records.length - 1) - i));\r
4142             }\r
4143         }\r
4144         this.toMultiselect.view.refresh();\r
4145         this.toMultiselect.view.select(selectionsArray);\r
4146     },\r
4147 \r
4148     toBottom : function() {\r
4149         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4150         var records = [];\r
4151         if (selectionsArray.length > 0) {\r
4152             selectionsArray.sort();\r
4153             for (var i=0; i<selectionsArray.length; i++) {\r
4154                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4155                 records.push(record);\r
4156             }\r
4157             selectionsArray = [];\r
4158             for (var i=0; i<records.length; i++) {\r
4159                 record = records[i];\r
4160                 this.toMultiselect.view.store.remove(record);\r
4161                 this.toMultiselect.view.store.add(record);\r
4162                 selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));\r
4163             }\r
4164         }\r
4165         this.toMultiselect.view.refresh();\r
4166         this.toMultiselect.view.select(selectionsArray);\r
4167     },\r
4168 \r
4169     up : function() {\r
4170         var record = null;\r
4171         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4172         selectionsArray.sort();\r
4173         var newSelectionsArray = [];\r
4174         if (selectionsArray.length > 0) {\r
4175             for (var i=0; i<selectionsArray.length; i++) {\r
4176                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4177                 if ((selectionsArray[i] - 1) >= 0) {\r
4178                     this.toMultiselect.view.store.remove(record);\r
4179                     this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);\r
4180                     newSelectionsArray.push(selectionsArray[i] - 1);\r
4181                 }\r
4182             }\r
4183             this.toMultiselect.view.refresh();\r
4184             this.toMultiselect.view.select(newSelectionsArray);\r
4185         }\r
4186     },\r
4187 \r
4188     down : function() {\r
4189         var record = null;\r
4190         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4191         selectionsArray.sort();\r
4192         selectionsArray.reverse();\r
4193         var newSelectionsArray = [];\r
4194         if (selectionsArray.length > 0) {\r
4195             for (var i=0; i<selectionsArray.length; i++) {\r
4196                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4197                 if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {\r
4198                     this.toMultiselect.view.store.remove(record);\r
4199                     this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);\r
4200                     newSelectionsArray.push(selectionsArray[i] + 1);\r
4201                 }\r
4202             }\r
4203             this.toMultiselect.view.refresh();\r
4204             this.toMultiselect.view.select(newSelectionsArray);\r
4205         }\r
4206     },\r
4207 \r
4208     fromTo : function() {\r
4209         var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();\r
4210         var records = [];\r
4211         if (selectionsArray.length > 0) {\r
4212             for (var i=0; i<selectionsArray.length; i++) {\r
4213                 record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);\r
4214                 records.push(record);\r
4215             }\r
4216             if(!this.allowDup)selectionsArray = [];\r
4217             for (var i=0; i<records.length; i++) {\r
4218                 record = records[i];\r
4219                 if(this.allowDup){\r
4220                     var x=new Ext.data.Record();\r
4221                     record.id=x.id;\r
4222                     delete x;\r
4223                     this.toMultiselect.view.store.add(record);\r
4224                 }else{\r
4225                     this.fromMultiselect.view.store.remove(record);\r
4226                     this.toMultiselect.view.store.add(record);\r
4227                     selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));\r
4228                 }\r
4229             }\r
4230         }\r
4231         this.toMultiselect.view.refresh();\r
4232         this.fromMultiselect.view.refresh();\r
4233         var si = this.toMultiselect.store.sortInfo;\r
4234         if(si){\r
4235             this.toMultiselect.store.sort(si.field, si.direction);\r
4236         }\r
4237         this.toMultiselect.view.select(selectionsArray);\r
4238     },\r
4239 \r
4240     toFrom : function() {\r
4241         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4242         var records = [];\r
4243         if (selectionsArray.length > 0) {\r
4244             for (var i=0; i<selectionsArray.length; i++) {\r
4245                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4246                 records.push(record);\r
4247             }\r
4248             selectionsArray = [];\r
4249             for (var i=0; i<records.length; i++) {\r
4250                 record = records[i];\r
4251                 this.toMultiselect.view.store.remove(record);\r
4252                 if(!this.allowDup){\r
4253                     this.fromMultiselect.view.store.add(record);\r
4254                     selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));\r
4255                 }\r
4256             }\r
4257         }\r
4258         this.fromMultiselect.view.refresh();\r
4259         this.toMultiselect.view.refresh();\r
4260         var si = this.fromMultiselect.store.sortInfo;\r
4261         if (si){\r
4262             this.fromMultiselect.store.sort(si.field, si.direction);\r
4263         }\r
4264         this.fromMultiselect.view.select(selectionsArray);\r
4265     },\r
4266 \r
4267     valueChanged: function(store) {\r
4268         var record = null;\r
4269         var values = [];\r
4270         for (var i=0; i<store.getCount(); i++) {\r
4271             record = store.getAt(i);\r
4272             values.push(record.get(this.toMultiselect.valueField));\r
4273         }\r
4274         this.hiddenField.dom.value = values.join(this.delimiter);\r
4275         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
4276     },\r
4277 \r
4278     getValue : function() {\r
4279         return this.hiddenField.dom.value;\r
4280     },\r
4281 \r
4282     onRowDblClick : function(vw, index, node, e) {\r
4283         if (vw == this.toMultiselect.view){\r
4284             this.toFrom();\r
4285         } else if (vw == this.fromMultiselect.view) {\r
4286             this.fromTo();\r
4287         }\r
4288         return this.fireEvent('rowdblclick', vw, index, node, e);\r
4289     },\r
4290 \r
4291     reset: function(){\r
4292         range = this.toMultiselect.store.getRange();\r
4293         this.toMultiselect.store.removeAll();\r
4294         this.fromMultiselect.store.add(range);\r
4295         var si = this.fromMultiselect.store.sortInfo;\r
4296         if (si){\r
4297             this.fromMultiselect.store.sort(si.field, si.direction);\r
4298         }\r
4299         this.valueChanged(this.toMultiselect.store);\r
4300     }\r
4301 });\r
4302 \r
4303 Ext.reg('itemselector', Ext.ux.form.ItemSelector);\r
4304 \r
4305 //backwards compat\r
4306 Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;\r
4307 Ext.ns('Ext.ux.form');\r
4308 \r
4309 /**\r
4310  * @class Ext.ux.form.MultiSelect\r
4311  * @extends Ext.form.Field\r
4312  * A control that allows selection and form submission of multiple list items.\r
4313  *\r
4314  *  @history\r
4315  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
4316  *    2008-06-19 bpm Docs and demo code clean up\r
4317  *\r
4318  * @constructor\r
4319  * Create a new MultiSelect\r
4320  * @param {Object} config Configuration options\r
4321  * @xtype multiselect \r
4322  */\r
4323 Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {\r
4324     /**\r
4325      * @cfg {String} legend Wraps the object with a fieldset and specified legend.\r
4326      */\r
4327     /**\r
4328      * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.\r
4329      */\r
4330     /**\r
4331      * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).\r
4332      */\r
4333     /**\r
4334      * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).\r
4335      */\r
4336     /**\r
4337      * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).\r
4338      */\r
4339     ddReorder:false,\r
4340     /**\r
4341      * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a\r
4342      * toolbar config, or an array of buttons/button configs to be added to the toolbar.\r
4343      */\r
4344     /**\r
4345      * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled\r
4346      * (use for lists which are sorted, defaults to false).\r
4347      */\r
4348     appendOnly:false,\r
4349     /**\r
4350      * @cfg {Number} width Width in pixels of the control (defaults to 100).\r
4351      */\r
4352     width:100,\r
4353     /**\r
4354      * @cfg {Number} height Height in pixels of the control (defaults to 100).\r
4355      */\r
4356     height:100,\r
4357     /**\r
4358      * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).\r
4359      */\r
4360     displayField:0,\r
4361     /**\r
4362      * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).\r
4363      */\r
4364     valueField:1,\r
4365     /**\r
4366      * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no\r
4367      * selection (defaults to true).\r
4368      */\r
4369     allowBlank:true,\r
4370     /**\r
4371      * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).\r
4372      */\r
4373     minSelections:0,\r
4374     /**\r
4375      * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).\r
4376      */\r
4377     maxSelections:Number.MAX_VALUE,\r
4378     /**\r
4379      * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as\r
4380      * {@link Ext.form.TextField#blankText}.\r
4381      */\r
4382     blankText:Ext.form.TextField.prototype.blankText,\r
4383     /**\r
4384      * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}\r
4385      * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.\r
4386      */\r
4387     minSelectionsText:'Minimum {0} item(s) required',\r
4388     /**\r
4389      * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}\r
4390      * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.\r
4391      */\r
4392     maxSelectionsText:'Maximum {0} item(s) allowed',\r
4393     /**\r
4394      * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values\r
4395      * (defaults to ',').\r
4396      */\r
4397     delimiter:',',\r
4398     /**\r
4399      * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).\r
4400      * Acceptable values for this property are:\r
4401      * <div class="mdetail-params"><ul>\r
4402      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>\r
4403      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.\r
4404      * <div class="mdetail-params"><ul>\r
4405      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">\r
4406      * A 1-dimensional array will automatically be expanded (each array item will be the combo\r
4407      * {@link #valueField value} and {@link #displayField text})</div></li>\r
4408      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">\r
4409      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo\r
4410      * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.\r
4411      * </div></li></ul></div></li></ul></div>\r
4412      */\r
4413 \r
4414     // private\r
4415     defaultAutoCreate : {tag: "div"},\r
4416 \r
4417     // private\r
4418     initComponent: function(){\r
4419         Ext.ux.form.MultiSelect.superclass.initComponent.call(this);\r
4420 \r
4421         if(Ext.isArray(this.store)){\r
4422             if (Ext.isArray(this.store[0])){\r
4423                 this.store = new Ext.data.ArrayStore({\r
4424                     fields: ['value','text'],\r
4425                     data: this.store\r
4426                 });\r
4427                 this.valueField = 'value';\r
4428             }else{\r
4429                 this.store = new Ext.data.ArrayStore({\r
4430                     fields: ['text'],\r
4431                     data: this.store,\r
4432                     expandData: true\r
4433                 });\r
4434                 this.valueField = 'text';\r
4435             }\r
4436             this.displayField = 'text';\r
4437         } else {\r
4438             this.store = Ext.StoreMgr.lookup(this.store);\r
4439         }\r
4440 \r
4441         this.addEvents({\r
4442             'dblclick' : true,\r
4443             'click' : true,\r
4444             'change' : true,\r
4445             'drop' : true\r
4446         });\r
4447     },\r
4448 \r
4449     // private\r
4450     onRender: function(ct, position){\r
4451         Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);\r
4452 \r
4453         var fs = this.fs = new Ext.form.FieldSet({\r
4454             renderTo: this.el,\r
4455             title: this.legend,\r
4456             height: this.height,\r
4457             width: this.width,\r
4458             style: "padding:0;",\r
4459             tbar: this.tbar\r
4460         });\r
4461         fs.body.addClass('ux-mselect');\r
4462 \r
4463         this.view = new Ext.ListView({\r
4464             multiSelect: true,\r
4465             store: this.store,\r
4466             columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],\r
4467             hideHeaders: true\r
4468         });\r
4469 \r
4470         fs.add(this.view);\r
4471 \r
4472         this.view.on('click', this.onViewClick, this);\r
4473         this.view.on('beforeclick', this.onViewBeforeClick, this);\r
4474         this.view.on('dblclick', this.onViewDblClick, this);\r
4475 \r
4476         this.hiddenName = this.name || Ext.id();\r
4477         var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };\r
4478         this.hiddenField = this.el.createChild(hiddenTag);\r
4479         this.hiddenField.dom.disabled = this.hiddenName != this.name;\r
4480         fs.doLayout();\r
4481     },\r
4482 \r
4483     // private\r
4484     afterRender: function(){\r
4485         Ext.ux.form.MultiSelect.superclass.afterRender.call(this);\r
4486 \r
4487         if (this.ddReorder && !this.dragGroup && !this.dropGroup){\r
4488             this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();\r
4489         }\r
4490 \r
4491         if (this.draggable || this.dragGroup){\r
4492             this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {\r
4493                 ddGroup: this.dragGroup\r
4494             });\r
4495         }\r
4496         if (this.droppable || this.dropGroup){\r
4497             this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {\r
4498                 ddGroup: this.dropGroup\r
4499             });\r
4500         }\r
4501     },\r
4502 \r
4503     // private\r
4504     onViewClick: function(vw, index, node, e) {\r
4505         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
4506         this.hiddenField.dom.value = this.getValue();\r
4507         this.fireEvent('click', this, e);\r
4508         this.validate();\r
4509     },\r
4510 \r
4511     // private\r
4512     onViewBeforeClick: function(vw, index, node, e) {\r
4513         if (this.disabled) {return false;}\r
4514     },\r
4515 \r
4516     // private\r
4517     onViewDblClick : function(vw, index, node, e) {\r
4518         return this.fireEvent('dblclick', vw, index, node, e);\r
4519     },\r
4520 \r
4521     /**\r
4522      * Returns an array of data values for the selected items in the list. The values will be separated\r
4523      * by {@link #delimiter}.\r
4524      * @return {Array} value An array of string data values\r
4525      */\r
4526     getValue: function(valueField){\r
4527         var returnArray = [];\r
4528         var selectionsArray = this.view.getSelectedIndexes();\r
4529         if (selectionsArray.length == 0) {return '';}\r
4530         for (var i=0; i<selectionsArray.length; i++) {\r
4531             returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));\r
4532         }\r
4533         return returnArray.join(this.delimiter);\r
4534     },\r
4535 \r
4536     /**\r
4537      * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.\r
4538      * @param {String/Array} values The values to set\r
4539      */\r
4540     setValue: function(values) {\r
4541         var index;\r
4542         var selections = [];\r
4543         this.view.clearSelections();\r
4544         this.hiddenField.dom.value = '';\r
4545 \r
4546         if (!values || (values == '')) { return; }\r
4547 \r
4548         if (!Ext.isArray(values)) { values = values.split(this.delimiter); }\r
4549         for (var i=0; i<values.length; i++) {\r
4550             index = this.view.store.indexOf(this.view.store.query(this.valueField,\r
4551                 new RegExp('^' + values[i] + '$', "i")).itemAt(0));\r
4552             selections.push(index);\r
4553         }\r
4554         this.view.select(selections);\r
4555         this.hiddenField.dom.value = this.getValue();\r
4556         this.validate();\r
4557     },\r
4558 \r
4559     // inherit docs\r
4560     reset : function() {\r
4561         this.setValue('');\r
4562     },\r
4563 \r
4564     // inherit docs\r
4565     getRawValue: function(valueField) {\r
4566         var tmp = this.getValue(valueField);\r
4567         if (tmp.length) {\r
4568             tmp = tmp.split(this.delimiter);\r
4569         }\r
4570         else {\r
4571             tmp = [];\r
4572         }\r
4573         return tmp;\r
4574     },\r
4575 \r
4576     // inherit docs\r
4577     setRawValue: function(values){\r
4578         setValue(values);\r
4579     },\r
4580 \r
4581     // inherit docs\r
4582     validateValue : function(value){\r
4583         if (value.length < 1) { // if it has no value\r
4584              if (this.allowBlank) {\r
4585                  this.clearInvalid();\r
4586                  return true;\r
4587              } else {\r
4588                  this.markInvalid(this.blankText);\r
4589                  return false;\r
4590              }\r
4591         }\r
4592         if (value.length < this.minSelections) {\r
4593             this.markInvalid(String.format(this.minSelectionsText, this.minSelections));\r
4594             return false;\r
4595         }\r
4596         if (value.length > this.maxSelections) {\r
4597             this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));\r
4598             return false;\r
4599         }\r
4600         return true;\r
4601     },\r
4602 \r
4603     // inherit docs\r
4604     disable: function(){\r
4605         this.disabled = true;\r
4606         this.hiddenField.dom.disabled = true;\r
4607         this.fs.disable();\r
4608     },\r
4609 \r
4610     // inherit docs\r
4611     enable: function(){\r
4612         this.disabled = false;\r
4613         this.hiddenField.dom.disabled = false;\r
4614         this.fs.enable();\r
4615     },\r
4616 \r
4617     // inherit docs\r
4618     destroy: function(){\r
4619         Ext.destroy(this.fs, this.dragZone, this.dropZone);\r
4620         Ext.ux.form.MultiSelect.superclass.destroy.call(this);\r
4621     }\r
4622 });\r
4623 \r
4624 \r
4625 Ext.reg('multiselect', Ext.ux.form.MultiSelect);\r
4626 \r
4627 //backwards compat\r
4628 Ext.ux.Multiselect = Ext.ux.form.MultiSelect;\r
4629 \r
4630 \r
4631 Ext.ux.form.MultiSelect.DragZone = function(ms, config){\r
4632     this.ms = ms;\r
4633     this.view = ms.view;\r
4634     var ddGroup = config.ddGroup || 'MultiselectDD';\r
4635     var dd;\r
4636     if (Ext.isArray(ddGroup)){\r
4637         dd = ddGroup.shift();\r
4638     } else {\r
4639         dd = ddGroup;\r
4640         ddGroup = null;\r
4641     }\r
4642     Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
4643     this.setDraggable(ddGroup);\r
4644 };\r
4645 \r
4646 Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {\r
4647     onInitDrag : function(x, y){\r
4648         var el = Ext.get(this.dragData.ddel.cloneNode(true));\r
4649         this.proxy.update(el.dom);\r
4650         el.setWidth(el.child('em').getWidth());\r
4651         this.onStartDrag(x, y);\r
4652         return true;\r
4653     },\r
4654     \r
4655     // private\r
4656     collectSelection: function(data) {\r
4657         data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();\r
4658         var i = 0;\r
4659         this.view.store.each(function(rec){\r
4660             if (this.view.isSelected(i)) {\r
4661                 var n = this.view.getNode(i);\r
4662                 var dragNode = n.cloneNode(true);\r
4663                 dragNode.id = Ext.id();\r
4664                 data.ddel.appendChild(dragNode);\r
4665                 data.records.push(this.view.store.getAt(i));\r
4666                 data.viewNodes.push(n);\r
4667             }\r
4668             i++;\r
4669         }, this);\r
4670     },\r
4671 \r
4672     // override\r
4673     onEndDrag: function(data, e) {\r
4674         var d = Ext.get(this.dragData.ddel);\r
4675         if (d && d.hasClass("multi-proxy")) {\r
4676             d.remove();\r
4677         }\r
4678     },\r
4679 \r
4680     // override\r
4681     getDragData: function(e){\r
4682         var target = this.view.findItemFromChild(e.getTarget());\r
4683         if(target) {\r
4684             if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {\r
4685                 this.view.select(target);\r
4686                 this.ms.setValue(this.ms.getValue());\r
4687             }\r
4688             if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;\r
4689             var dragData = {\r
4690                 sourceView: this.view,\r
4691                 viewNodes: [],\r
4692                 records: []\r
4693             };\r
4694             if (this.view.getSelectionCount() == 1) {\r
4695                 var i = this.view.getSelectedIndexes()[0];\r
4696                 var n = this.view.getNode(i);\r
4697                 dragData.viewNodes.push(dragData.ddel = n);\r
4698                 dragData.records.push(this.view.store.getAt(i));\r
4699                 dragData.repairXY = Ext.fly(n).getXY();\r
4700             } else {\r
4701                 dragData.ddel = document.createElement('div');\r
4702                 dragData.ddel.className = 'multi-proxy';\r
4703                 this.collectSelection(dragData);\r
4704             }\r
4705             return dragData;\r
4706         }\r
4707         return false;\r
4708     },\r
4709 \r
4710     // override the default repairXY.\r
4711     getRepairXY : function(e){\r
4712         return this.dragData.repairXY;\r
4713     },\r
4714 \r
4715     // private\r
4716     setDraggable: function(ddGroup){\r
4717         if (!ddGroup) return;\r
4718         if (Ext.isArray(ddGroup)) {\r
4719             Ext.each(ddGroup, this.setDraggable, this);\r
4720             return;\r
4721         }\r
4722         this.addToGroup(ddGroup);\r
4723     }\r
4724 });\r
4725 \r
4726 Ext.ux.form.MultiSelect.DropZone = function(ms, config){\r
4727     this.ms = ms;\r
4728     this.view = ms.view;\r
4729     var ddGroup = config.ddGroup || 'MultiselectDD';\r
4730     var dd;\r
4731     if (Ext.isArray(ddGroup)){\r
4732         dd = ddGroup.shift();\r
4733     } else {\r
4734         dd = ddGroup;\r
4735         ddGroup = null;\r
4736     }\r
4737     Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
4738     this.setDroppable(ddGroup);\r
4739 };\r
4740 \r
4741 Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {\r
4742     /**\r
4743          * Part of the Ext.dd.DropZone interface. If no target node is found, the\r
4744          * whole Element becomes the target, and this causes the drop gesture to append.\r
4745          */\r
4746     getTargetFromEvent : function(e) {\r
4747         var target = e.getTarget();\r
4748         return target;\r
4749     },\r
4750 \r
4751     // private\r
4752     getDropPoint : function(e, n, dd){\r
4753         if (n == this.ms.fs.body.dom) { return "below"; }\r
4754         var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;\r
4755         var c = t + (b - t) / 2;\r
4756         var y = Ext.lib.Event.getPageY(e);\r
4757         if(y <= c) {\r
4758             return "above";\r
4759         }else{\r
4760             return "below";\r
4761         }\r
4762     },\r
4763 \r
4764     // private\r
4765     isValidDropPoint: function(pt, n, data) {\r
4766         if (!data.viewNodes || (data.viewNodes.length != 1)) {\r
4767             return true;\r
4768         }\r
4769         var d = data.viewNodes[0];\r
4770         if (d == n) {\r
4771             return false;\r
4772         }\r
4773         if ((pt == "below") && (n.nextSibling == d)) {\r
4774             return false;\r
4775         }\r
4776         if ((pt == "above") && (n.previousSibling == d)) {\r
4777             return false;\r
4778         }\r
4779         return true;\r
4780     },\r
4781 \r
4782     // override\r
4783     onNodeEnter : function(n, dd, e, data){\r
4784         return false;\r
4785     },\r
4786 \r
4787     // override\r
4788     onNodeOver : function(n, dd, e, data){\r
4789         var dragElClass = this.dropNotAllowed;\r
4790         var pt = this.getDropPoint(e, n, dd);\r
4791         if (this.isValidDropPoint(pt, n, data)) {\r
4792             if (this.ms.appendOnly) {\r
4793                 return "x-tree-drop-ok-below";\r
4794             }\r
4795 \r
4796             // set the insert point style on the target node\r
4797             if (pt) {\r
4798                 var targetElClass;\r
4799                 if (pt == "above"){\r
4800                     dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";\r
4801                     targetElClass = "x-view-drag-insert-above";\r
4802                 } else {\r
4803                     dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";\r
4804                     targetElClass = "x-view-drag-insert-below";\r
4805                 }\r
4806                 if (this.lastInsertClass != targetElClass){\r
4807                     Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);\r
4808                     this.lastInsertClass = targetElClass;\r
4809                 }\r
4810             }\r
4811         }\r
4812         return dragElClass;\r
4813     },\r
4814 \r
4815     // private\r
4816     onNodeOut : function(n, dd, e, data){\r
4817         this.removeDropIndicators(n);\r
4818     },\r
4819 \r
4820     // private\r
4821     onNodeDrop : function(n, dd, e, data){\r
4822         if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {\r
4823             return false;\r
4824         }\r
4825         var pt = this.getDropPoint(e, n, dd);\r
4826         if (n != this.ms.fs.body.dom)\r
4827             n = this.view.findItemFromChild(n);\r
4828         var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n);\r
4829         if (pt == "below") {\r
4830             insertAt++;\r
4831         }\r
4832 \r
4833         var dir = false;\r
4834 \r
4835         // Validate if dragging within the same MultiSelect\r
4836         if (data.sourceView == this.view) {\r
4837             // If the first element to be inserted below is the target node, remove it\r
4838             if (pt == "below") {\r
4839                 if (data.viewNodes[0] == n) {\r
4840                     data.viewNodes.shift();\r
4841                 }\r
4842             } else {  // If the last element to be inserted above is the target node, remove it\r
4843                 if (data.viewNodes[data.viewNodes.length - 1] == n) {\r
4844                     data.viewNodes.pop();\r
4845                 }\r
4846             }\r
4847 \r
4848             // Nothing to drop...\r
4849             if (!data.viewNodes.length) {\r
4850                 return false;\r
4851             }\r
4852 \r
4853             // If we are moving DOWN, then because a store.remove() takes place first,\r
4854             // the insertAt must be decremented.\r
4855             if (insertAt > this.view.store.indexOf(data.records[0])) {\r
4856                 dir = 'down';\r
4857                 insertAt--;\r
4858             }\r
4859         }\r
4860 \r
4861         for (var i = 0; i < data.records.length; i++) {\r
4862             var r = data.records[i];\r
4863             if (data.sourceView) {\r
4864                 data.sourceView.store.remove(r);\r
4865             }\r
4866             this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);\r
4867             var si = this.view.store.sortInfo;\r
4868             if(si){\r
4869                 this.view.store.sort(si.field, si.direction);\r
4870             }\r
4871         }\r
4872         return true;\r
4873     },\r
4874 \r
4875     // private\r
4876     removeDropIndicators : function(n){\r
4877         if(n){\r
4878             Ext.fly(n).removeClass([\r
4879                 "x-view-drag-insert-above",\r
4880                 "x-view-drag-insert-left",\r
4881                 "x-view-drag-insert-right",\r
4882                 "x-view-drag-insert-below"]);\r
4883             this.lastInsertClass = "_noclass";\r
4884         }\r
4885     },\r
4886 \r
4887     // private\r
4888     setDroppable: function(ddGroup){\r
4889         if (!ddGroup) return;\r
4890         if (Ext.isArray(ddGroup)) {\r
4891             Ext.each(ddGroup, this.setDroppable, this);\r
4892             return;\r
4893         }\r
4894         this.addToGroup(ddGroup);\r
4895     }\r
4896 });\r
4897
4898 /* Fix for Opera, which does not seem to include the map function on Array's */
4899 if (!Array.prototype.map) {
4900     Array.prototype.map = function(fun){
4901         var len = this.length;
4902         if (typeof fun != 'function') {
4903             throw new TypeError();
4904         }
4905         var res = new Array(len);
4906         var thisp = arguments[1];
4907         for (var i = 0; i < len; i++) {
4908             if (i in this) {
4909                 res[i] = fun.call(thisp, this[i], i, this);
4910             }
4911         }
4912         return res;
4913     };
4914 }
4915
4916 Ext.ns('Ext.ux.data');
4917
4918 /**
4919  * @class Ext.ux.data.PagingMemoryProxy
4920  * @extends Ext.data.MemoryProxy
4921  * <p>Paging Memory Proxy, allows to use paging grid with in memory dataset</p>
4922  */
4923 Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, {
4924     constructor : function(data){
4925         Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this);
4926         this.data = data;
4927     },
4928     doRequest : function(action, rs, params, reader, callback, scope, options){
4929         params = params ||
4930         {};
4931         var result;
4932         try {
4933             result = reader.readRecords(this.data);
4934         } 
4935         catch (e) {
4936             this.fireEvent('loadexception', this, options, null, e);
4937             callback.call(scope, null, options, false);
4938             return;
4939         }
4940         
4941         // filtering
4942         if (params.filter !== undefined) {
4943             result.records = result.records.filter(function(el){
4944                 if (typeof(el) == 'object') {
4945                     var att = params.filterCol || 0;
4946                     return String(el.data[att]).match(params.filter) ? true : false;
4947                 }
4948                 else {
4949                     return String(el).match(params.filter) ? true : false;
4950                 }
4951             });
4952             result.totalRecords = result.records.length;
4953         }
4954         
4955         // sorting
4956         if (params.sort !== undefined) {
4957             // use integer as params.sort to specify column, since arrays are not named
4958             // params.sort=0; would also match a array without columns
4959             var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1;
4960             var fn = function(r1, r2){
4961                 return r1 < r2;
4962             };
4963             result.records.sort(function(a, b){
4964                 var v = 0;
4965                 if (typeof(a) == 'object') {
4966                     v = fn(a.data[params.sort], b.data[params.sort]) * dir;
4967                 }
4968                 else {
4969                     v = fn(a, b) * dir;
4970                 }
4971                 if (v == 0) {
4972                     v = (a.index < b.index ? -1 : 1);
4973                 }
4974                 return v;
4975             });
4976         }
4977         // paging (use undefined cause start can also be 0 (thus false))
4978         if (params.start !== undefined && params.limit !== undefined) {
4979             result.records = result.records.slice(params.start, params.start + params.limit);
4980         }
4981         callback.call(scope, result, options, true);
4982     }
4983 });
4984
4985 //backwards compat.
4986 Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
4987 Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {\r
4988     minHeight: 0,\r
4989     maxHeight:10000000,\r
4990 \r
4991     constructor: function(config){\r
4992         Ext.apply(this, config);\r
4993         this.events = {};\r
4994         Ext.ux.PanelResizer.superclass.constructor.call(this, config);\r
4995     },\r
4996 \r
4997     init : function(p){\r
4998         this.panel = p;\r
4999 \r
5000         if(this.panel.elements.indexOf('footer')==-1){\r
5001             p.elements += ',footer';\r
5002         }\r
5003         p.on('render', this.onRender, this);\r
5004     },\r
5005 \r
5006     onRender : function(p){\r
5007         this.handle = p.footer.createChild({cls:'x-panel-resize'});\r
5008 \r
5009         this.tracker = new Ext.dd.DragTracker({\r
5010             onStart: this.onDragStart.createDelegate(this),\r
5011             onDrag: this.onDrag.createDelegate(this),\r
5012             onEnd: this.onDragEnd.createDelegate(this),\r
5013             tolerance: 3,\r
5014             autoStart: 300\r
5015         });\r
5016         this.tracker.initEl(this.handle);\r
5017         p.on('beforedestroy', this.tracker.destroy, this.tracker);\r
5018     },\r
5019 \r
5020         // private\r
5021     onDragStart: function(e){\r
5022         this.dragging = true;\r
5023         this.startHeight = this.panel.el.getHeight();\r
5024         this.fireEvent('dragstart', this, e);\r
5025     },\r
5026 \r
5027         // private\r
5028     onDrag: function(e){\r
5029         this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));\r
5030         this.fireEvent('drag', this, e);\r
5031     },\r
5032 \r
5033         // private\r
5034     onDragEnd: function(e){\r
5035         this.dragging = false;\r
5036         this.fireEvent('dragend', this, e);\r
5037     }\r
5038 });\r
5039 Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {\r
5040     layout : 'column',\r
5041     autoScroll : true,\r
5042     cls : 'x-portal',\r
5043     defaultType : 'portalcolumn',\r
5044     \r
5045     initComponent : function(){\r
5046         Ext.ux.Portal.superclass.initComponent.call(this);\r
5047         this.addEvents({\r
5048             validatedrop:true,\r
5049             beforedragover:true,\r
5050             dragover:true,\r
5051             beforedrop:true,\r
5052             drop:true\r
5053         });\r
5054     },\r
5055 \r
5056     initEvents : function(){\r
5057         Ext.ux.Portal.superclass.initEvents.call(this);\r
5058         this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);\r
5059     },\r
5060     \r
5061     beforeDestroy : function() {\r
5062         if(this.dd){\r
5063             this.dd.unreg();\r
5064         }\r
5065         Ext.ux.Portal.superclass.beforeDestroy.call(this);\r
5066     }\r
5067 });\r
5068 \r
5069 Ext.reg('portal', Ext.ux.Portal);\r
5070 \r
5071 \r
5072 Ext.ux.Portal.DropZone = function(portal, cfg){\r
5073     this.portal = portal;\r
5074     Ext.dd.ScrollManager.register(portal.body);\r
5075     Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);\r
5076     portal.body.ddScrollConfig = this.ddScrollConfig;\r
5077 };\r
5078 \r
5079 Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {\r
5080     ddScrollConfig : {\r
5081         vthresh: 50,\r
5082         hthresh: -1,\r
5083         animate: true,\r
5084         increment: 200\r
5085     },\r
5086 \r
5087     createEvent : function(dd, e, data, col, c, pos){\r
5088         return {\r
5089             portal: this.portal,\r
5090             panel: data.panel,\r
5091             columnIndex: col,\r
5092             column: c,\r
5093             position: pos,\r
5094             data: data,\r
5095             source: dd,\r
5096             rawEvent: e,\r
5097             status: this.dropAllowed\r
5098         };\r
5099     },\r
5100 \r
5101     notifyOver : function(dd, e, data){\r
5102         var xy = e.getXY(), portal = this.portal, px = dd.proxy;\r
5103 \r
5104         // case column widths\r
5105         if(!this.grid){\r
5106             this.grid = this.getGrid();\r
5107         }\r
5108 \r
5109         // handle case scroll where scrollbars appear during drag\r
5110         var cw = portal.body.dom.clientWidth;\r
5111         if(!this.lastCW){\r
5112             this.lastCW = cw;\r
5113         }else if(this.lastCW != cw){\r
5114             this.lastCW = cw;\r
5115             portal.doLayout();\r
5116             this.grid = this.getGrid();\r
5117         }\r
5118 \r
5119         // determine column\r
5120         var col = 0, xs = this.grid.columnX, cmatch = false;\r
5121         for(var len = xs.length; col < len; col++){\r
5122             if(xy[0] < (xs[col].x + xs[col].w)){\r
5123                 cmatch = true;\r
5124                 break;\r
5125             }\r
5126         }\r
5127         // no match, fix last index\r
5128         if(!cmatch){\r
5129             col--;\r
5130         }\r
5131 \r
5132         // find insert position\r
5133         var p, match = false, pos = 0,\r
5134             c = portal.items.itemAt(col),\r
5135             items = c.items.items, overSelf = false;\r
5136 \r
5137         for(var len = items.length; pos < len; pos++){\r
5138             p = items[pos];\r
5139             var h = p.el.getHeight();\r
5140             if(h === 0){\r
5141                 overSelf = true;\r
5142             }\r
5143             else if((p.el.getY()+(h/2)) > xy[1]){\r
5144                 match = true;\r
5145                 break;\r
5146             }\r
5147         }\r
5148 \r
5149         pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);\r
5150         var overEvent = this.createEvent(dd, e, data, col, c, pos);\r
5151 \r
5152         if(portal.fireEvent('validatedrop', overEvent) !== false &&\r
5153            portal.fireEvent('beforedragover', overEvent) !== false){\r
5154 \r
5155             // make sure proxy width is fluid\r
5156             px.getProxy().setWidth('auto');\r
5157 \r
5158             if(p){\r
5159                 px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);\r
5160             }else{\r
5161                 px.moveProxy(c.el.dom, null);\r
5162             }\r
5163 \r
5164             this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};\r
5165             this.scrollPos = portal.body.getScroll();\r
5166 \r
5167             portal.fireEvent('dragover', overEvent);\r
5168 \r
5169             return overEvent.status;\r
5170         }else{\r
5171             return overEvent.status;\r
5172         }\r
5173 \r
5174     },\r
5175 \r
5176     notifyOut : function(){\r
5177         delete this.grid;\r
5178     },\r
5179 \r
5180     notifyDrop : function(dd, e, data){\r
5181         delete this.grid;\r
5182         if(!this.lastPos){\r
5183             return;\r
5184         }\r
5185         var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;\r
5186 \r
5187         var dropEvent = this.createEvent(dd, e, data, col, c,\r
5188             pos !== false ? pos : c.items.getCount());\r
5189 \r
5190         if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&\r
5191            this.portal.fireEvent('beforedrop', dropEvent) !== false){\r
5192 \r
5193             dd.proxy.getProxy().remove();\r
5194             dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);\r
5195             \r
5196             if(pos !== false){\r
5197                 if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){\r
5198                     pos++;\r
5199                 }\r
5200                 c.insert(pos, dd.panel);\r
5201             }else{\r
5202                 c.add(dd.panel);\r
5203             }\r
5204             \r
5205             c.doLayout();\r
5206 \r
5207             this.portal.fireEvent('drop', dropEvent);\r
5208 \r
5209             // scroll position is lost on drop, fix it\r
5210             var st = this.scrollPos.top;\r
5211             if(st){\r
5212                 var d = this.portal.body.dom;\r
5213                 setTimeout(function(){\r
5214                     d.scrollTop = st;\r
5215                 }, 10);\r
5216             }\r
5217 \r
5218         }\r
5219         delete this.lastPos;\r
5220     },\r
5221 \r
5222     // internal cache of body and column coords\r
5223     getGrid : function(){\r
5224         var box = this.portal.bwrap.getBox();\r
5225         box.columnX = [];\r
5226         this.portal.items.each(function(c){\r
5227              box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});\r
5228         });\r
5229         return box;\r
5230     },\r
5231 \r
5232     // unregister the dropzone from ScrollManager\r
5233     unreg: function() {\r
5234         //Ext.dd.ScrollManager.unregister(this.portal.body);\r
5235         Ext.ux.Portal.DropZone.superclass.unreg.call(this);\r
5236     }\r
5237 });\r
5238 Ext.ux.PortalColumn = Ext.extend(Ext.Container, {\r
5239     layout : 'anchor',\r
5240     //autoEl : 'div',//already defined by Ext.Component\r
5241     defaultType : 'portlet',\r
5242     cls : 'x-portal-column'\r
5243 });\r
5244 \r
5245 Ext.reg('portalcolumn', Ext.ux.PortalColumn);\r
5246 Ext.ux.Portlet = Ext.extend(Ext.Panel, {\r
5247     anchor : '100%',\r
5248     frame : true,\r
5249     collapsible : true,\r
5250     draggable : true,\r
5251     cls : 'x-portlet'\r
5252 });\r
5253 \r
5254 Ext.reg('portlet', Ext.ux.Portlet);\r
5255 /**
5256 * @class Ext.ux.ProgressBarPager
5257 * @extends Object 
5258 * Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text
5259
5260 * @ptype progressbarpager 
5261 * @constructor
5262 * Create a new ItemSelector
5263 * @param {Object} config Configuration options
5264 * @xtype itemselector 
5265 */
5266 Ext.ux.ProgressBarPager  = Ext.extend(Object, {
5267         /**
5268         * @cfg {Integer} progBarWidth
5269         * <p>The default progress bar width.  Default is 225.</p>
5270         */
5271         progBarWidth   : 225,
5272         /**
5273         * @cfg {String} defaultText
5274         * <p>The text to display while the store is loading.  Default is 'Loading...'</p>
5275         */
5276         defaultText    : 'Loading...',
5277         /**
5278         * @cfg {Object} defaultAnimCfg 
5279         * <p>A {@link Ext.Fx Ext.Fx} configuration object.  Default is  { duration : 1, easing : 'bounceOut' }.</p>
5280         */
5281         defaultAnimCfg : {
5282                 duration   : 1,
5283                 easing     : 'bounceOut'        
5284         },                                                                                                
5285         constructor : function(config) {
5286                 if (config) {
5287                         Ext.apply(this, config);
5288                 }
5289         },
5290         //public
5291         init : function (parent) {
5292         
5293         if(parent.displayInfo){
5294             this.parent = parent;
5295             var ind  = parent.items.indexOf(parent.displayItem);
5296             parent.remove(parent.displayItem, true);
5297             this.progressBar = new Ext.ProgressBar({
5298                 text    : this.defaultText,
5299                 width   : this.progBarWidth,
5300                 animate :  this.defaultAnimCfg
5301             });                 
5302            
5303             parent.displayItem = this.progressBar;
5304             
5305             parent.add(parent.displayItem); 
5306             parent.doLayout();
5307             Ext.apply(parent, this.parentOverrides);        
5308             
5309             this.progressBar.on('render', function(pb) {
5310                 pb.mon(pb.getEl().applyStyles('cursor:pointer'), 'click', this.handleProgressBarClick, this);
5311             }, this, {single: true});
5312                         
5313         }
5314           
5315     },
5316         // private
5317         // This method handles the click for the progress bar
5318         handleProgressBarClick : function(e){
5319                 var parent = this.parent;
5320                 var displayItem = parent.displayItem;
5321                 
5322                 var box = this.progressBar.getBox();
5323                 var xy = e.getXY();
5324                 var position = xy[0]-box.x;
5325                 var pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize);
5326                 
5327                 var newpage = Math.ceil(position/(displayItem.width/pages));
5328                 parent.changePage(newpage);
5329         },
5330         
5331         // private, overriddes
5332         parentOverrides  : {
5333                 // private
5334                 // This method updates the information via the progress bar.
5335                 updateInfo : function(){
5336                         if(this.displayItem){
5337                                 var count   = this.store.getCount();
5338                                 var pgData  = this.getPageData();
5339                                 var pageNum = this.readPage(pgData);
5340                                 
5341                                 var msg    = count == 0 ?
5342                                         this.emptyMsg :
5343                                         String.format(
5344                                                 this.displayMsg,
5345                                                 this.cursor+1, this.cursor+count, this.store.getTotalCount()
5346                                         );
5347                                         
5348                                 pageNum = pgData.activePage; ;  
5349                                 
5350                                 var pct = pageNum / pgData.pages;       
5351                                 
5352                                 this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig);
5353                         }
5354                 }
5355         }
5356 });
5357 Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
5358
5359 Ext.ns('Ext.ux.grid');
5360
5361 /**
5362  * @class Ext.ux.grid.RowEditor
5363  * @extends Ext.Panel 
5364  * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
5365  * A validation mode may be enabled which uses AnchorTips to notify the user of all
5366  * validation errors at once.
5367  * 
5368  * @ptype roweditor
5369  */
5370 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
5371     floating: true,
5372     shadow: false,
5373     layout: 'hbox',
5374     cls: 'x-small-editor',
5375     buttonAlign: 'center',
5376     baseCls: 'x-row-editor',
5377     elements: 'header,footer,body',
5378     frameWidth: 5,
5379     buttonPad: 3,
5380     clicksToEdit: 'auto',
5381     monitorValid: true,
5382     focusDelay: 250,
5383     errorSummary: true,
5384     
5385     saveText: 'Save',
5386     cancelText: 'Cancel',
5387     commitChangesText: 'You need to commit or cancel your changes',
5388     errorText: 'Errors',
5389
5390     defaults: {
5391         normalWidth: true
5392     },
5393
5394     initComponent: function(){
5395         Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
5396         this.addEvents(
5397             /**
5398              * @event beforeedit
5399              * Fired before the row editor is activated.
5400              * If the listener returns <tt>false</tt> the editor will not be activated.
5401              * @param {Ext.ux.grid.RowEditor} roweditor This object
5402              * @param {Number} rowIndex The rowIndex of the row just edited
5403              */
5404             'beforeedit',
5405             /**
5406              * @event canceledit
5407              * Fired when the editor is cancelled.
5408              * @param {Ext.ux.grid.RowEditor} roweditor This object
5409              * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid. 
5410              */
5411             'canceledit',
5412             /**
5413              * @event validateedit
5414              * Fired after a row is edited and passes validation.
5415              * If the listener returns <tt>false</tt> changes to the record will not be set.
5416              * @param {Ext.ux.grid.RowEditor} roweditor This object
5417              * @param {Object} changes Object with changes made to the record.
5418              * @param {Ext.data.Record} r The Record that was edited.
5419              * @param {Number} rowIndex The rowIndex of the row just edited
5420              */
5421             'validateedit',
5422             /**
5423              * @event afteredit
5424              * Fired after a row is edited and passes validation.  This event is fired
5425              * after the store's update event is fired with this edit.
5426              * @param {Ext.ux.grid.RowEditor} roweditor This object
5427              * @param {Object} changes Object with changes made to the record.
5428              * @param {Ext.data.Record} r The Record that was edited.
5429              * @param {Number} rowIndex The rowIndex of the row just edited
5430              */
5431             'afteredit'
5432         );
5433     },
5434
5435     init: function(grid){
5436         this.grid = grid;
5437         this.ownerCt = grid;
5438         if(this.clicksToEdit === 2){
5439             grid.on('rowdblclick', this.onRowDblClick, this);
5440         }else{
5441             grid.on('rowclick', this.onRowClick, this);
5442             if(Ext.isIE){
5443                 grid.on('rowdblclick', this.onRowDblClick, this);
5444             }
5445         }
5446
5447         // stopEditing without saving when a record is removed from Store.
5448         grid.getStore().on('remove', function() {
5449             this.stopEditing(false);
5450         },this);
5451
5452         grid.on({
5453             scope: this,
5454             keydown: this.onGridKey,
5455             columnresize: this.verifyLayout,
5456             columnmove: this.refreshFields,
5457             reconfigure: this.refreshFields,
5458             beforedestroy : this.beforedestroy,
5459             destroy : this.destroy,
5460             bodyscroll: {
5461                 buffer: 250,
5462                 fn: this.positionButtons
5463             }
5464         });
5465         grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
5466         grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
5467     },
5468
5469     beforedestroy: function() {
5470         this.grid.getStore().un('remove', this.onStoreRemove, this);
5471         this.stopEditing(false);
5472         Ext.destroy(this.btns);
5473     },
5474
5475     refreshFields: function(){
5476         this.initFields();
5477         this.verifyLayout();
5478     },
5479
5480     isDirty: function(){
5481         var dirty;
5482         this.items.each(function(f){
5483             if(String(this.values[f.id]) !== String(f.getValue())){
5484                 dirty = true;
5485                 return false;
5486             }
5487         }, this);
5488         return dirty;
5489     },
5490
5491     startEditing: function(rowIndex, doFocus){
5492         if(this.editing && this.isDirty()){
5493             this.showTooltip(this.commitChangesText);
5494             return;
5495         }
5496         if(Ext.isObject(rowIndex)){
5497             rowIndex = this.grid.getStore().indexOf(rowIndex);
5498         }
5499         if(this.fireEvent('beforeedit', this, rowIndex) !== false){
5500             this.editing = true;
5501             var g = this.grid, view = g.getView(),
5502                 row = view.getRow(rowIndex),
5503                 record = g.store.getAt(rowIndex);
5504                 
5505             this.record = record;
5506             this.rowIndex = rowIndex;
5507             this.values = {};
5508             if(!this.rendered){
5509                 this.render(view.getEditorParent());
5510             }
5511             var w = Ext.fly(row).getWidth();
5512             this.setSize(w);
5513             if(!this.initialized){
5514                 this.initFields();
5515             }
5516             var cm = g.getColumnModel(), fields = this.items.items, f, val;
5517             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
5518                 val = this.preEditValue(record, cm.getDataIndex(i));
5519                 f = fields[i];
5520                 f.setValue(val);
5521                 this.values[f.id] = Ext.isEmpty(val) ? '' : val;
5522             }
5523             this.verifyLayout(true);
5524             if(!this.isVisible()){
5525                 this.setPagePosition(Ext.fly(row).getXY());
5526             } else{
5527                 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
5528             }
5529             if(!this.isVisible()){
5530                 this.show().doLayout();
5531             }
5532             if(doFocus !== false){
5533                 this.doFocus.defer(this.focusDelay, this);
5534             }
5535         }
5536     },
5537
5538     stopEditing : function(saveChanges){
5539         this.editing = false;
5540         if(!this.isVisible()){
5541             return;
5542         }
5543         if(saveChanges === false || !this.isValid()){
5544             this.hide();
5545             this.fireEvent('canceledit', this, saveChanges === false);
5546             return;
5547         }
5548         var changes = {}, 
5549             r = this.record, 
5550             hasChange = false,
5551             cm = this.grid.colModel, 
5552             fields = this.items.items;
5553         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
5554             if(!cm.isHidden(i)){
5555                 var dindex = cm.getDataIndex(i);
5556                 if(!Ext.isEmpty(dindex)){
5557                     var oldValue = r.data[dindex],
5558                         value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
5559                     if(String(oldValue) !== String(value)){
5560                         changes[dindex] = value;
5561                         hasChange = true;
5562                     }
5563                 }
5564             }
5565         }
5566         if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
5567             r.beginEdit();
5568             Ext.iterate(changes, function(name, value){
5569                 r.set(name, value);
5570             });
5571             r.endEdit();
5572             this.fireEvent('afteredit', this, changes, r, this.rowIndex);
5573         }
5574         this.hide();
5575     },
5576
5577     verifyLayout: function(force){
5578         if(this.el && (this.isVisible() || force === true)){
5579             var row = this.grid.getView().getRow(this.rowIndex);
5580             this.setSize(Ext.fly(row).getWidth(), Ext.fly(row).getHeight() + 9);
5581             var cm = this.grid.colModel, fields = this.items.items;
5582             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
5583                 if(!cm.isHidden(i)){
5584                     var adjust = 0;
5585                     if(i === (len - 1)){
5586                         adjust += 3; // outer padding
5587                     } else{
5588                         adjust += 1;
5589                     }
5590                     fields[i].show();
5591                     fields[i].setWidth(cm.getColumnWidth(i) - adjust);
5592                 } else{
5593                     fields[i].hide();
5594                 }
5595             }
5596             this.doLayout();
5597             this.positionButtons();
5598         }
5599     },
5600
5601     slideHide : function(){
5602         this.hide();
5603     },
5604
5605     initFields: function(){
5606         var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
5607         this.removeAll(false);
5608         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
5609             var c = cm.getColumnAt(i),
5610                 ed = c.getEditor();
5611             if(!ed){
5612                 ed = c.displayEditor || new Ext.form.DisplayField();
5613             }
5614             if(i == 0){
5615                 ed.margins = pm('0 1 2 1');
5616             } else if(i == len - 1){
5617                 ed.margins = pm('0 0 2 1');
5618             } else{
5619                 ed.margins = pm('0 1 2');
5620             }
5621             ed.setWidth(cm.getColumnWidth(i));
5622             ed.column = c;
5623             if(ed.ownerCt !== this){
5624                 ed.on('focus', this.ensureVisible, this);
5625                 ed.on('specialkey', this.onKey, this);
5626             }
5627             this.insert(i, ed);
5628         }
5629         this.initialized = true;
5630     },
5631
5632     onKey: function(f, e){
5633         if(e.getKey() === e.ENTER){
5634             this.stopEditing(true);
5635             e.stopPropagation();
5636         }
5637     },
5638
5639     onGridKey: function(e){
5640         if(e.getKey() === e.ENTER && !this.isVisible()){
5641             var r = this.grid.getSelectionModel().getSelected();
5642             if(r){
5643                 var index = this.grid.store.indexOf(r);
5644                 this.startEditing(index);
5645                 e.stopPropagation();
5646             }
5647         }
5648     },
5649
5650     ensureVisible: function(editor){
5651         if(this.isVisible()){
5652              this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
5653         }
5654     },
5655
5656     onRowClick: function(g, rowIndex, e){
5657         if(this.clicksToEdit == 'auto'){
5658             var li = this.lastClickIndex;
5659             this.lastClickIndex = rowIndex;
5660             if(li != rowIndex && !this.isVisible()){
5661                 return;
5662             }
5663         }
5664         this.startEditing(rowIndex, false);
5665         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
5666     },
5667
5668     onRowDblClick: function(g, rowIndex, e){
5669         this.startEditing(rowIndex, false);
5670         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
5671     },
5672
5673     onRender: function(){
5674         Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
5675         this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
5676         this.btns = new Ext.Panel({
5677             baseCls: 'x-plain',
5678             cls: 'x-btns',
5679             elements:'body',
5680             layout: 'table',
5681             width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
5682             items: [{
5683                 ref: 'saveBtn',
5684                 itemId: 'saveBtn',
5685                 xtype: 'button',
5686                 text: this.saveText,
5687                 width: this.minButtonWidth,
5688                 handler: this.stopEditing.createDelegate(this, [true])
5689             }, {
5690                 xtype: 'button',
5691                 text: this.cancelText,
5692                 width: this.minButtonWidth,
5693                 handler: this.stopEditing.createDelegate(this, [false])
5694             }]
5695         });
5696         this.btns.render(this.bwrap);
5697     },
5698
5699     afterRender: function(){
5700         Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
5701         this.positionButtons();
5702         if(this.monitorValid){
5703             this.startMonitoring();
5704         }
5705     },
5706
5707     onShow: function(){
5708         if(this.monitorValid){
5709             this.startMonitoring();
5710         }
5711         Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
5712     },
5713
5714     onHide: function(){
5715         Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
5716         this.stopMonitoring();
5717         this.grid.getView().focusRow(this.rowIndex);
5718     },
5719
5720     positionButtons: function(){
5721         if(this.btns){
5722             var g = this.grid,
5723                 h = this.el.dom.clientHeight,
5724                 view = g.getView(),
5725                 scroll = view.scroller.dom.scrollLeft,
5726                 bw = this.btns.getWidth(),
5727                 width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());
5728                 
5729             this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
5730         }
5731     },
5732
5733     // private
5734     preEditValue : function(r, field){
5735         var value = r.data[field];
5736         return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
5737     },
5738
5739     // private
5740     postEditValue : function(value, originalValue, r, field){
5741         return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
5742     },
5743
5744     doFocus: function(pt){
5745         if(this.isVisible()){
5746             var index = 0,
5747                 cm = this.grid.getColumnModel(),
5748                 c;
5749             if(pt){
5750                 index = this.getTargetColumnIndex(pt);
5751             }
5752             for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
5753                 c = cm.getColumnAt(i);
5754                 if(!c.hidden && c.getEditor()){
5755                     c.getEditor().focus();
5756                     break;
5757                 }
5758             }
5759         }
5760     },
5761
5762     getTargetColumnIndex: function(pt){
5763         var grid = this.grid, 
5764             v = grid.view,
5765             x = pt.left,
5766             cms = grid.colModel.config,
5767             i = 0, 
5768             match = false;
5769         for(var len = cms.length, c; c = cms[i]; i++){
5770             if(!c.hidden){
5771                 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
5772                     match = i;
5773                     break;
5774                 }
5775             }
5776         }
5777         return match;
5778     },
5779
5780     startMonitoring : function(){
5781         if(!this.bound && this.monitorValid){
5782             this.bound = true;
5783             Ext.TaskMgr.start({
5784                 run : this.bindHandler,
5785                 interval : this.monitorPoll || 200,
5786                 scope: this
5787             });
5788         }
5789     },
5790
5791     stopMonitoring : function(){
5792         this.bound = false;
5793         if(this.tooltip){
5794             this.tooltip.hide();
5795         }
5796     },
5797
5798     isValid: function(){
5799         var valid = true;
5800         this.items.each(function(f){
5801             if(!f.isValid(true)){
5802                 valid = false;
5803                 return false;
5804             }
5805         });
5806         return valid;
5807     },
5808
5809     // private
5810     bindHandler : function(){
5811         if(!this.bound){
5812             return false; // stops binding
5813         }
5814         var valid = this.isValid();
5815         if(!valid && this.errorSummary){
5816             this.showTooltip(this.getErrorText().join(''));
5817         }
5818         this.btns.saveBtn.setDisabled(!valid);
5819         this.fireEvent('validation', this, valid);
5820     },
5821
5822     showTooltip: function(msg){
5823         var t = this.tooltip;
5824         if(!t){
5825             t = this.tooltip = new Ext.ToolTip({
5826                 maxWidth: 600,
5827                 cls: 'errorTip',
5828                 width: 300,
5829                 title: this.errorText,
5830                 autoHide: false,
5831                 anchor: 'left',
5832                 anchorToTarget: true,
5833                 mouseOffset: [40,0]
5834             });
5835         }
5836         var v = this.grid.getView(),
5837             top = parseInt(this.el.dom.style.top, 10),
5838             scroll = v.scroller.dom.scrollTop,
5839             h = this.el.getHeight();
5840                 
5841         if(top + h >= scroll){
5842             t.initTarget(this.items.last().getEl());
5843             if(!t.rendered){
5844                 t.show();
5845                 t.hide();
5846             }
5847             t.body.update(msg);
5848             t.doAutoWidth();
5849             t.show();
5850         }else if(t.rendered){
5851             t.hide();
5852         }
5853     },
5854
5855     getErrorText: function(){
5856         var data = ['<ul>'];
5857         this.items.each(function(f){
5858             if(!f.isValid(true)){
5859                 data.push('<li>', f.activeError, '</li>');
5860             }
5861         });
5862         data.push('</ul>');
5863         return data;
5864     }
5865 });
5866 Ext.preg('roweditor', Ext.ux.grid.RowEditor);
5867
5868 Ext.override(Ext.form.Field, {
5869     markInvalid : function(msg){
5870         if(!this.rendered || this.preventMark){ // not rendered
5871             return;
5872         }
5873         msg = msg || this.invalidText;
5874
5875         var mt = this.getMessageHandler();
5876         if(mt){
5877             mt.mark(this, msg);
5878         }else if(this.msgTarget){
5879             this.el.addClass(this.invalidClass);
5880             var t = Ext.getDom(this.msgTarget);
5881             if(t){
5882                 t.innerHTML = msg;
5883                 t.style.display = this.msgDisplay;
5884             }
5885         }
5886         this.activeError = msg;
5887         this.fireEvent('invalid', this, msg);
5888     }
5889 });
5890
5891 Ext.override(Ext.ToolTip, {
5892     doAutoWidth : function(){
5893         var bw = this.body.getTextWidth();
5894         if(this.title){
5895             bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
5896         }
5897         bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
5898         this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
5899
5900         // IE7 repaint bug on initial show
5901         if(Ext.isIE7 && !this.repainted){
5902             this.el.repaint();
5903             this.repainted = true;
5904         }
5905     }
5906 });
5907 Ext.ns('Ext.ux.grid');\r
5908 \r
5909 /**\r
5910  * @class Ext.ux.grid.RowExpander\r
5911  * @extends Ext.util.Observable\r
5912  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables\r
5913  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react\r
5914  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.\r
5915  *\r
5916  * @ptype rowexpander\r
5917  */\r
5918 Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {\r
5919     /**\r
5920      * @cfg {Boolean} expandOnEnter\r
5921      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter\r
5922      * key is pressed (defaults to <tt>true</tt>).\r
5923      */\r
5924     expandOnEnter : true,\r
5925     /**\r
5926      * @cfg {Boolean} expandOnDblClick\r
5927      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked\r
5928      * (defaults to <tt>true</tt>).\r
5929      */\r
5930     expandOnDblClick : true,\r
5931 \r
5932     header : '',\r
5933     width : 20,\r
5934     sortable : false,\r
5935     fixed : true,\r
5936     menuDisabled : true,\r
5937     dataIndex : '',\r
5938     id : 'expander',\r
5939     lazyRender : true,\r
5940     enableCaching : true,\r
5941 \r
5942     constructor: function(config){\r
5943         Ext.apply(this, config);\r
5944 \r
5945         this.addEvents({\r
5946             /**\r
5947              * @event beforeexpand\r
5948              * Fires before the row expands. Have the listener return false to prevent the row from expanding.\r
5949              * @param {Object} this RowExpander object.\r
5950              * @param {Object} Ext.data.Record Record for the selected row.\r
5951              * @param {Object} body body element for the secondary row.\r
5952              * @param {Number} rowIndex The current row index.\r
5953              */\r
5954             beforeexpand: true,\r
5955             /**\r
5956              * @event expand\r
5957              * Fires after the row expands.\r
5958              * @param {Object} this RowExpander object.\r
5959              * @param {Object} Ext.data.Record Record for the selected row.\r
5960              * @param {Object} body body element for the secondary row.\r
5961              * @param {Number} rowIndex The current row index.\r
5962              */\r
5963             expand: true,\r
5964             /**\r
5965              * @event beforecollapse\r
5966              * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.\r
5967              * @param {Object} this RowExpander object.\r
5968              * @param {Object} Ext.data.Record Record for the selected row.\r
5969              * @param {Object} body body element for the secondary row.\r
5970              * @param {Number} rowIndex The current row index.\r
5971              */\r
5972             beforecollapse: true,\r
5973             /**\r
5974              * @event collapse\r
5975              * Fires after the row collapses.\r
5976              * @param {Object} this RowExpander object.\r
5977              * @param {Object} Ext.data.Record Record for the selected row.\r
5978              * @param {Object} body body element for the secondary row.\r
5979              * @param {Number} rowIndex The current row index.\r
5980              */\r
5981             collapse: true\r
5982         });\r
5983 \r
5984         Ext.ux.grid.RowExpander.superclass.constructor.call(this);\r
5985 \r
5986         if(this.tpl){\r
5987             if(typeof this.tpl == 'string'){\r
5988                 this.tpl = new Ext.Template(this.tpl);\r
5989             }\r
5990             this.tpl.compile();\r
5991         }\r
5992 \r
5993         this.state = {};\r
5994         this.bodyContent = {};\r
5995     },\r
5996 \r
5997     getRowClass : function(record, rowIndex, p, ds){\r
5998         p.cols = p.cols-1;\r
5999         var content = this.bodyContent[record.id];\r
6000         if(!content && !this.lazyRender){\r
6001             content = this.getBodyContent(record, rowIndex);\r
6002         }\r
6003         if(content){\r
6004             p.body = content;\r
6005         }\r
6006         return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';\r
6007     },\r
6008 \r
6009     init : function(grid){\r
6010         this.grid = grid;\r
6011 \r
6012         var view = grid.getView();\r
6013         view.getRowClass = this.getRowClass.createDelegate(this);\r
6014 \r
6015         view.enableRowBody = true;\r
6016 \r
6017 \r
6018         grid.on('render', this.onRender, this);\r
6019         grid.on('destroy', this.onDestroy, this);\r
6020     },\r
6021 \r
6022     // @private\r
6023     onRender: function() {\r
6024         var grid = this.grid;\r
6025         var mainBody = grid.getView().mainBody;\r
6026         mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});\r
6027         if (this.expandOnEnter) {\r
6028             this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {\r
6029                 'enter' : this.onEnter,\r
6030                 scope: this\r
6031             });\r
6032         }\r
6033         if (this.expandOnDblClick) {\r
6034             grid.on('rowdblclick', this.onRowDblClick, this);\r
6035         }\r
6036     },\r
6037     \r
6038     // @private    \r
6039     onDestroy: function() {\r
6040         if(this.keyNav){\r
6041             this.keyNav.disable();\r
6042             delete this.keyNav;\r
6043         }\r
6044         /*\r
6045          * A majority of the time, the plugin will be destroyed along with the grid,\r
6046          * which means the mainBody won't be available. On the off chance that the plugin\r
6047          * isn't destroyed with the grid, take care of removing the listener.\r
6048          */\r
6049         var mainBody = this.grid.getView().mainBody;\r
6050         if(mainBody){\r
6051             mainBody.un('mousedown', this.onMouseDown, this);\r
6052         }\r
6053     },\r
6054     // @private\r
6055     onRowDblClick: function(grid, rowIdx, e) {\r
6056         this.toggleRow(rowIdx);\r
6057     },\r
6058 \r
6059     onEnter: function(e) {\r
6060         var g = this.grid;\r
6061         var sm = g.getSelectionModel();\r
6062         var sels = sm.getSelections();\r
6063         for (var i = 0, len = sels.length; i < len; i++) {\r
6064             var rowIdx = g.getStore().indexOf(sels[i]);\r
6065             this.toggleRow(rowIdx);\r
6066         }\r
6067     },\r
6068 \r
6069     getBodyContent : function(record, index){\r
6070         if(!this.enableCaching){\r
6071             return this.tpl.apply(record.data);\r
6072         }\r
6073         var content = this.bodyContent[record.id];\r
6074         if(!content){\r
6075             content = this.tpl.apply(record.data);\r
6076             this.bodyContent[record.id] = content;\r
6077         }\r
6078         return content;\r
6079     },\r
6080 \r
6081     onMouseDown : function(e, t){\r
6082         e.stopEvent();\r
6083         var row = e.getTarget('.x-grid3-row');\r
6084         this.toggleRow(row);\r
6085     },\r
6086 \r
6087     renderer : function(v, p, record){\r
6088         p.cellAttr = 'rowspan="2"';\r
6089         return '<div class="x-grid3-row-expander">&#160;</div>';\r
6090     },\r
6091 \r
6092     beforeExpand : function(record, body, rowIndex){\r
6093         if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){\r
6094             if(this.tpl && this.lazyRender){\r
6095                 body.innerHTML = this.getBodyContent(record, rowIndex);\r
6096             }\r
6097             return true;\r
6098         }else{\r
6099             return false;\r
6100         }\r
6101     },\r
6102 \r
6103     toggleRow : function(row){\r
6104         if(typeof row == 'number'){\r
6105             row = this.grid.view.getRow(row);\r
6106         }\r
6107         this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);\r
6108     },\r
6109 \r
6110     expandRow : function(row){\r
6111         if(typeof row == 'number'){\r
6112             row = this.grid.view.getRow(row);\r
6113         }\r
6114         var record = this.grid.store.getAt(row.rowIndex);\r
6115         var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);\r
6116         if(this.beforeExpand(record, body, row.rowIndex)){\r
6117             this.state[record.id] = true;\r
6118             Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');\r
6119             this.fireEvent('expand', this, record, body, row.rowIndex);\r
6120         }\r
6121     },\r
6122 \r
6123     collapseRow : function(row){\r
6124         if(typeof row == 'number'){\r
6125             row = this.grid.view.getRow(row);\r
6126         }\r
6127         var record = this.grid.store.getAt(row.rowIndex);\r
6128         var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);\r
6129         if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){\r
6130             this.state[record.id] = false;\r
6131             Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');\r
6132             this.fireEvent('collapse', this, record, body, row.rowIndex);\r
6133         }\r
6134     }\r
6135 });\r
6136 \r
6137 Ext.preg('rowexpander', Ext.ux.grid.RowExpander);\r
6138 \r
6139 //backwards compat\r
6140 Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not
6141 // exist by default in Ext, so we have to add the namespace first:
6142 Ext.ns('Ext.ux.layout');
6143
6144 /**
6145  * @class Ext.ux.layout.RowLayout
6146  * @extends Ext.layout.ContainerLayout
6147  * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
6148  * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
6149  * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
6150  * and should generally not need to be created directly via the new keyword.</p>
6151  * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
6152  * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
6153  * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
6154  * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
6155  * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
6156  * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and
6157  * less than 1 (e.g., .25).</p>
6158  * <p>The basic rules for specifying row heights are pretty simple.  The logic makes two passes through the
6159  * set of contained panels.  During the first layout pass, all panels that either have a fixed height or none
6160  * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
6161  * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
6162  * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
6163  * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
6164  * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your
6165  * layout may not render as expected.  Example usage:</p>
6166  * <pre><code>
6167 // All rows are percentages -- they must add up to 1
6168 var p = new Ext.Panel({
6169     title: 'Row Layout - Percentage Only',
6170     layout:'ux.row',
6171     items: [{
6172         title: 'Row 1',
6173         rowHeight: .25
6174     },{
6175         title: 'Row 2',
6176         rowHeight: .6
6177     },{
6178         title: 'Row 3',
6179         rowHeight: .15
6180     }]
6181 });
6182
6183 // Mix of height and rowHeight -- all rowHeight values must add
6184 // up to 1. The first row will take up exactly 120px, and the last two
6185 // rows will fill the remaining container height.
6186 var p = new Ext.Panel({
6187     title: 'Row Layout - Mixed',
6188     layout:'ux.row',
6189     items: [{
6190         title: 'Row 1',
6191         height: 120,
6192         // standard panel widths are still supported too:
6193         width: '50%' // or 200
6194     },{
6195         title: 'Row 2',
6196         rowHeight: .8,
6197         width: 300
6198     },{
6199         title: 'Row 3',
6200         rowHeight: .2
6201     }]
6202 });
6203 </code></pre>
6204  */
6205 Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
6206     // private
6207     monitorResize:true,
6208
6209     // private
6210     isValidParent : function(c, target){
6211         return c.getEl().dom.parentNode == this.innerCt.dom;
6212     },
6213
6214     // private
6215     onLayout : function(ct, target){
6216         var rs = ct.items.items, len = rs.length, r, i;
6217
6218         if(!this.innerCt){
6219             target.addClass('ux-row-layout-ct');
6220             this.innerCt = target.createChild({cls:'x-row-inner'});
6221         }
6222         this.renderAll(ct, this.innerCt);
6223
6224         var size = target.getViewSize();
6225
6226         if(size.width < 1 && size.height < 1){ // display none?
6227             return;
6228         }
6229
6230         var h = size.height - target.getPadding('tb'),
6231             ph = h;
6232
6233         this.innerCt.setSize({height:h});
6234
6235         // some rows can be percentages while others are fixed
6236         // so we need to make 2 passes
6237
6238         for(i = 0; i < len; i++){
6239             r = rs[i];
6240             if(!r.rowHeight){
6241                 ph -= (r.getSize().height + r.getEl().getMargins('tb'));
6242             }
6243         }
6244
6245         ph = ph < 0 ? 0 : ph;
6246
6247         for(i = 0; i < len; i++){
6248             r = rs[i];
6249             if(r.rowHeight){
6250                 r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
6251             }
6252         }
6253     }
6254
6255     /**
6256      * @property activeItem
6257      * @hide
6258      */
6259 });
6260
6261 Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
6262 Ext.ns('Ext.ux.form');\r
6263 \r
6264 Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {\r
6265     initComponent : function(){\r
6266         Ext.ux.form.SearchField.superclass.initComponent.call(this);\r
6267         this.on('specialkey', function(f, e){\r
6268             if(e.getKey() == e.ENTER){\r
6269                 this.onTrigger2Click();\r
6270             }\r
6271         }, this);\r
6272     },\r
6273 \r
6274     validationEvent:false,\r
6275     validateOnBlur:false,\r
6276     trigger1Class:'x-form-clear-trigger',\r
6277     trigger2Class:'x-form-search-trigger',\r
6278     hideTrigger1:true,\r
6279     width:180,\r
6280     hasSearch : false,\r
6281     paramName : 'query',\r
6282 \r
6283     onTrigger1Click : function(){\r
6284         if(this.hasSearch){\r
6285             this.el.dom.value = '';\r
6286             var o = {start: 0};\r
6287             this.store.baseParams = this.store.baseParams || {};\r
6288             this.store.baseParams[this.paramName] = '';\r
6289             this.store.reload({params:o});\r
6290             this.triggers[0].hide();\r
6291             this.hasSearch = false;\r
6292         }\r
6293     },\r
6294 \r
6295     onTrigger2Click : function(){\r
6296         var v = this.getRawValue();\r
6297         if(v.length < 1){\r
6298             this.onTrigger1Click();\r
6299             return;\r
6300         }\r
6301         var o = {start: 0};\r
6302         this.store.baseParams = this.store.baseParams || {};\r
6303         this.store.baseParams[this.paramName] = v;\r
6304         this.store.reload({params:o});\r
6305         this.hasSearch = true;\r
6306         this.triggers[0].show();\r
6307     }\r
6308 });Ext.ns('Ext.ux.form');\r
6309 \r
6310 /**\r
6311  * @class Ext.ux.form.SelectBox\r
6312  * @extends Ext.form.ComboBox\r
6313  * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging\r
6314  * through the list, with item selection occurring when the mouse button is released.\r
6315  * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}\r
6316  * on inner elements.  Re-enabling editable after calling this will NOT work.</p>\r
6317  * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392\r
6318  * @history 2007-07-08 jvs\r
6319  * Slight mods for Ext 2.0\r
6320  * @xtype selectbox\r
6321  */\r
6322 Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {\r
6323     constructor: function(config){\r
6324         this.searchResetDelay = 1000;\r
6325         config = config || {};\r
6326         config = Ext.apply(config || {}, {\r
6327             editable: false,\r
6328             forceSelection: true,\r
6329             rowHeight: false,\r
6330             lastSearchTerm: false,\r
6331             triggerAction: 'all',\r
6332             mode: 'local'\r
6333         });\r
6334 \r
6335         Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);\r
6336 \r
6337         this.lastSelectedIndex = this.selectedIndex || 0;\r
6338     },\r
6339 \r
6340     initEvents : function(){\r
6341         Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);\r
6342         // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE\r
6343         this.el.on('keydown', this.keySearch, this, true);\r
6344         this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);\r
6345     },\r
6346 \r
6347     keySearch : function(e, target, options) {\r
6348         var raw = e.getKey();\r
6349         var key = String.fromCharCode(raw);\r
6350         var startIndex = 0;\r
6351 \r
6352         if( !this.store.getCount() ) {\r
6353             return;\r
6354         }\r
6355 \r
6356         switch(raw) {\r
6357             case Ext.EventObject.HOME:\r
6358                 e.stopEvent();\r
6359                 this.selectFirst();\r
6360                 return;\r
6361 \r
6362             case Ext.EventObject.END:\r
6363                 e.stopEvent();\r
6364                 this.selectLast();\r
6365                 return;\r
6366 \r
6367             case Ext.EventObject.PAGEDOWN:\r
6368                 this.selectNextPage();\r
6369                 e.stopEvent();\r
6370                 return;\r
6371 \r
6372             case Ext.EventObject.PAGEUP:\r
6373                 this.selectPrevPage();\r
6374                 e.stopEvent();\r
6375                 return;\r
6376         }\r
6377 \r
6378         // skip special keys other than the shift key\r
6379         if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {\r
6380             return;\r
6381         }\r
6382         if( this.lastSearchTerm == key ) {\r
6383             startIndex = this.lastSelectedIndex;\r
6384         }\r
6385         this.search(this.displayField, key, startIndex);\r
6386         this.cshTask.delay(this.searchResetDelay);\r
6387     },\r
6388 \r
6389     onRender : function(ct, position) {\r
6390         this.store.on('load', this.calcRowsPerPage, this);\r
6391         Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);\r
6392         if( this.mode == 'local' ) {\r
6393             this.initList();\r
6394             this.calcRowsPerPage();\r
6395         }\r
6396     },\r
6397 \r
6398     onSelect : function(record, index, skipCollapse){\r
6399         if(this.fireEvent('beforeselect', this, record, index) !== false){\r
6400             this.setValue(record.data[this.valueField || this.displayField]);\r
6401             if( !skipCollapse ) {\r
6402                 this.collapse();\r
6403             }\r
6404             this.lastSelectedIndex = index + 1;\r
6405             this.fireEvent('select', this, record, index);\r
6406         }\r
6407     },\r
6408 \r
6409     afterRender : function() {\r
6410         Ext.ux.form.SelectBox.superclass.afterRender.apply(this, arguments);\r
6411         if(Ext.isWebKit) {\r
6412             this.el.swallowEvent('mousedown', true);\r
6413         }\r
6414         this.el.unselectable();\r
6415         this.innerList.unselectable();\r
6416         this.trigger.unselectable();\r
6417         this.innerList.on('mouseup', function(e, target, options) {\r
6418             if( target.id && target.id == this.innerList.id ) {\r
6419                 return;\r
6420             }\r
6421             this.onViewClick();\r
6422         }, this);\r
6423 \r
6424         this.innerList.on('mouseover', function(e, target, options) {\r
6425             if( target.id && target.id == this.innerList.id ) {\r
6426                 return;\r
6427             }\r
6428             this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;\r
6429             this.cshTask.delay(this.searchResetDelay);\r
6430         }, this);\r
6431 \r
6432         this.trigger.un('click', this.onTriggerClick, this);\r
6433         this.trigger.on('mousedown', function(e, target, options) {\r
6434             e.preventDefault();\r
6435             this.onTriggerClick();\r
6436         }, this);\r
6437 \r
6438         this.on('collapse', function(e, target, options) {\r
6439             Ext.getDoc().un('mouseup', this.collapseIf, this);\r
6440         }, this, true);\r
6441 \r
6442         this.on('expand', function(e, target, options) {\r
6443             Ext.getDoc().on('mouseup', this.collapseIf, this);\r
6444         }, this, true);\r
6445     },\r
6446 \r
6447     clearSearchHistory : function() {\r
6448         this.lastSelectedIndex = 0;\r
6449         this.lastSearchTerm = false;\r
6450     },\r
6451 \r
6452     selectFirst : function() {\r
6453         this.focusAndSelect(this.store.data.first());\r
6454     },\r
6455 \r
6456     selectLast : function() {\r
6457         this.focusAndSelect(this.store.data.last());\r
6458     },\r
6459 \r
6460     selectPrevPage : function() {\r
6461         if( !this.rowHeight ) {\r
6462             return;\r
6463         }\r
6464         var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);\r
6465         this.focusAndSelect(this.store.getAt(index));\r
6466     },\r
6467 \r
6468     selectNextPage : function() {\r
6469         if( !this.rowHeight ) {\r
6470             return;\r
6471         }\r
6472         var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);\r
6473         this.focusAndSelect(this.store.getAt(index));\r
6474     },\r
6475 \r
6476     search : function(field, value, startIndex) {\r
6477         field = field || this.displayField;\r
6478         this.lastSearchTerm = value;\r
6479         var index = this.store.find.apply(this.store, arguments);\r
6480         if( index !== -1 ) {\r
6481             this.focusAndSelect(index);\r
6482         }\r
6483     },\r
6484 \r
6485     focusAndSelect : function(record) {\r
6486         var index = Ext.isNumber(record) ? record : this.store.indexOf(record);\r
6487         this.select(index, this.isExpanded());\r
6488         this.onSelect(this.store.getAt(index), index, this.isExpanded());\r
6489     },\r
6490 \r
6491     calcRowsPerPage : function() {\r
6492         if( this.store.getCount() ) {\r
6493             this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();\r
6494             this.rowsPerPage = this.maxHeight / this.rowHeight;\r
6495         } else {\r
6496             this.rowHeight = false;\r
6497         }\r
6498     }\r
6499 \r
6500 });\r
6501 \r
6502 Ext.reg('selectbox', Ext.ux.form.SelectBox);\r
6503 \r
6504 //backwards compat\r
6505 Ext.ux.SelectBox = Ext.ux.form.SelectBox;\r
6506 /**\r
6507  * @class Ext.ux.SliderTip\r
6508  * @extends Ext.Tip\r
6509  * Simple plugin for using an Ext.Tip with a slider to show the slider value\r
6510  */\r
6511 Ext.ux.SliderTip = Ext.extend(Ext.Tip, {\r
6512     minWidth: 10,\r
6513     offsets : [0, -10],\r
6514     init : function(slider){\r
6515         slider.on('dragstart', this.onSlide, this);\r
6516         slider.on('drag', this.onSlide, this);\r
6517         slider.on('dragend', this.hide, this);\r
6518         slider.on('destroy', this.destroy, this);\r
6519     },\r
6520 \r
6521     onSlide : function(slider){\r
6522         this.show();\r
6523         this.body.update(this.getText(slider));\r
6524         this.doAutoWidth();\r
6525         this.el.alignTo(slider.thumb, 'b-t?', this.offsets);\r
6526     },\r
6527 \r
6528     getText : function(slider){\r
6529         return String(slider.getValue());\r
6530     }\r
6531 });\r
6532 Ext.ux.SlidingPager = Ext.extend(Object, {\r
6533     init : function(pbar){\r
6534         Ext.each(pbar.items.getRange(2,6), function(c){\r
6535             c.hide();\r
6536         });\r
6537         var slider = new Ext.Slider({\r
6538             width: 114,\r
6539             minValue: 1,\r
6540             maxValue: 1,\r
6541             plugins: new Ext.ux.SliderTip({\r
6542                 getText : function(s){\r
6543                     return String.format('Page <b>{0}</b> of <b>{1}</b>', s.value, s.maxValue);\r
6544                 }\r
6545             }),\r
6546             listeners: {\r
6547                 changecomplete: function(s, v){\r
6548                     pbar.changePage(v);\r
6549                 }\r
6550             }\r
6551         });\r
6552         pbar.insert(5, slider);\r
6553         pbar.on({\r
6554             change: function(pb, data){\r
6555                 slider.maxValue = data.pages;\r
6556                 slider.setValue(data.activePage);\r
6557             },\r
6558             beforedestroy: function(){\r
6559                 slider.destroy();\r
6560             }\r
6561         });\r
6562     }\r
6563 });Ext.ns('Ext.ux.form');\r
6564 \r
6565 /**\r
6566  * @class Ext.ux.form.SpinnerField\r
6567  * @extends Ext.form.NumberField\r
6568  * Creates a field utilizing Ext.ux.Spinner\r
6569  * @xtype spinnerfield\r
6570  */\r
6571 Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {\r
6572     actionMode: 'wrap',\r
6573     deferHeight: true,\r
6574     autoSize: Ext.emptyFn,\r
6575     onBlur: Ext.emptyFn,\r
6576     adjustSize: Ext.BoxComponent.prototype.adjustSize,\r
6577 \r
6578         constructor: function(config) {\r
6579                 var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');\r
6580 \r
6581                 var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);\r
6582 \r
6583                 var plugins = config.plugins\r
6584                         ? (Ext.isArray(config.plugins)\r
6585                                 ? config.plugins.push(spl)\r
6586                                 : [config.plugins, spl])\r
6587                         : spl;\r
6588 \r
6589                 Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));\r
6590         },\r
6591 \r
6592     // private\r
6593     getResizeEl: function(){\r
6594         return this.wrap;\r
6595     },\r
6596 \r
6597     // private\r
6598     getPositionEl: function(){\r
6599         return this.wrap;\r
6600     },\r
6601 \r
6602     // private\r
6603     alignErrorIcon: function(){\r
6604         if (this.wrap) {\r
6605             this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);\r
6606         }\r
6607     },\r
6608 \r
6609     validateBlur: function(){\r
6610         return true;\r
6611     }\r
6612 });\r
6613 \r
6614 Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);\r
6615 \r
6616 //backwards compat\r
6617 Ext.form.SpinnerField = Ext.ux.form.SpinnerField;\r
6618 /**\r
6619  * @class Ext.ux.Spinner\r
6620  * @extends Ext.util.Observable\r
6621  * Creates a Spinner control utilized by Ext.ux.form.SpinnerField\r
6622  */\r
6623 Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {\r
6624     incrementValue: 1,\r
6625     alternateIncrementValue: 5,\r
6626     triggerClass: 'x-form-spinner-trigger',\r
6627     splitterClass: 'x-form-spinner-splitter',\r
6628     alternateKey: Ext.EventObject.shiftKey,\r
6629     defaultValue: 0,\r
6630     accelerate: false,\r
6631 \r
6632     constructor: function(config){\r
6633         Ext.ux.Spinner.superclass.constructor.call(this, config);\r
6634         Ext.apply(this, config);\r
6635         this.mimicing = false;\r
6636     },\r
6637 \r
6638     init: function(field){\r
6639         this.field = field;\r
6640 \r
6641         field.afterMethod('onRender', this.doRender, this);\r
6642         field.afterMethod('onEnable', this.doEnable, this);\r
6643         field.afterMethod('onDisable', this.doDisable, this);\r
6644         field.afterMethod('afterRender', this.doAfterRender, this);\r
6645         field.afterMethod('onResize', this.doResize, this);\r
6646         field.afterMethod('onFocus', this.doFocus, this);\r
6647         field.beforeMethod('onDestroy', this.doDestroy, this);\r
6648     },\r
6649 \r
6650     doRender: function(ct, position){\r
6651         var el = this.el = this.field.getEl();\r
6652         var f = this.field;\r
6653 \r
6654         if (!f.wrap) {\r
6655             f.wrap = this.wrap = el.wrap({\r
6656                 cls: "x-form-field-wrap"\r
6657             });\r
6658         }\r
6659         else {\r
6660             this.wrap = f.wrap.addClass('x-form-field-wrap');\r
6661         }\r
6662 \r
6663         this.trigger = this.wrap.createChild({\r
6664             tag: "img",\r
6665             src: Ext.BLANK_IMAGE_URL,\r
6666             cls: "x-form-trigger " + this.triggerClass\r
6667         });\r
6668 \r
6669         if (!f.width) {\r
6670             this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());\r
6671         }\r
6672 \r
6673         this.splitter = this.wrap.createChild({\r
6674             tag: 'div',\r
6675             cls: this.splitterClass,\r
6676             style: 'width:13px; height:2px;'\r
6677         });\r
6678         this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();\r
6679 \r
6680         this.proxy = this.trigger.createProxy('', this.splitter, true);\r
6681         this.proxy.addClass("x-form-spinner-proxy");\r
6682         this.proxy.setStyle('left', '0px');\r
6683         this.proxy.setSize(14, 1);\r
6684         this.proxy.hide();\r
6685         this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {\r
6686             dragElId: this.proxy.id\r
6687         });\r
6688 \r
6689         this.initTrigger();\r
6690         this.initSpinner();\r
6691     },\r
6692 \r
6693     doAfterRender: function(){\r
6694         var y;\r
6695         if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {\r
6696             this.el.position();\r
6697             this.el.setY(y);\r
6698         }\r
6699     },\r
6700 \r
6701     doEnable: function(){\r
6702         if (this.wrap) {\r
6703             this.wrap.removeClass(this.field.disabledClass);\r
6704         }\r
6705     },\r
6706 \r
6707     doDisable: function(){\r
6708         if (this.wrap) {\r
6709             this.wrap.addClass(this.field.disabledClass);\r
6710             this.el.removeClass(this.field.disabledClass);\r
6711         }\r
6712     },\r
6713 \r
6714     doResize: function(w, h){\r
6715         if (typeof w == 'number') {\r
6716             this.el.setWidth(w - this.trigger.getWidth());\r
6717         }\r
6718         this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());\r
6719     },\r
6720 \r
6721     doFocus: function(){\r
6722         if (!this.mimicing) {\r
6723             this.wrap.addClass('x-trigger-wrap-focus');\r
6724             this.mimicing = true;\r
6725             Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {\r
6726                 delay: 10\r
6727             });\r
6728             this.el.on('keydown', this.checkTab, this);\r
6729         }\r
6730     },\r
6731 \r
6732     // private\r
6733     checkTab: function(e){\r
6734         if (e.getKey() == e.TAB) {\r
6735             this.triggerBlur();\r
6736         }\r
6737     },\r
6738 \r
6739     // private\r
6740     mimicBlur: function(e){\r
6741         if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {\r
6742             this.triggerBlur();\r
6743         }\r
6744     },\r
6745 \r
6746     // private\r
6747     triggerBlur: function(){\r
6748         this.mimicing = false;\r
6749         Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);\r
6750         this.el.un("keydown", this.checkTab, this);\r
6751         this.field.beforeBlur();\r
6752         this.wrap.removeClass('x-trigger-wrap-focus');\r
6753         this.field.onBlur.call(this.field);\r
6754     },\r
6755 \r
6756     initTrigger: function(){\r
6757         this.trigger.addClassOnOver('x-form-trigger-over');\r
6758         this.trigger.addClassOnClick('x-form-trigger-click');\r
6759     },\r
6760 \r
6761     initSpinner: function(){\r
6762         this.field.addEvents({\r
6763             'spin': true,\r
6764             'spinup': true,\r
6765             'spindown': true\r
6766         });\r
6767 \r
6768         this.keyNav = new Ext.KeyNav(this.el, {\r
6769             "up": function(e){\r
6770                 e.preventDefault();\r
6771                 this.onSpinUp();\r
6772             },\r
6773 \r
6774             "down": function(e){\r
6775                 e.preventDefault();\r
6776                 this.onSpinDown();\r
6777             },\r
6778 \r
6779             "pageUp": function(e){\r
6780                 e.preventDefault();\r
6781                 this.onSpinUpAlternate();\r
6782             },\r
6783 \r
6784             "pageDown": function(e){\r
6785                 e.preventDefault();\r
6786                 this.onSpinDownAlternate();\r
6787             },\r
6788 \r
6789             scope: this\r
6790         });\r
6791 \r
6792         this.repeater = new Ext.util.ClickRepeater(this.trigger, {\r
6793             accelerate: this.accelerate\r
6794         });\r
6795         this.field.mon(this.repeater, "click", this.onTriggerClick, this, {\r
6796             preventDefault: true\r
6797         });\r
6798 \r
6799         this.field.mon(this.trigger, {\r
6800             mouseover: this.onMouseOver,\r
6801             mouseout: this.onMouseOut,\r
6802             mousemove: this.onMouseMove,\r
6803             mousedown: this.onMouseDown,\r
6804             mouseup: this.onMouseUp,\r
6805             scope: this,\r
6806             preventDefault: true\r
6807         });\r
6808 \r
6809         this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);\r
6810 \r
6811         this.dd.setXConstraint(0, 0, 10)\r
6812         this.dd.setYConstraint(1500, 1500, 10);\r
6813         this.dd.endDrag = this.endDrag.createDelegate(this);\r
6814         this.dd.startDrag = this.startDrag.createDelegate(this);\r
6815         this.dd.onDrag = this.onDrag.createDelegate(this);\r
6816     },\r
6817 \r
6818     onMouseOver: function(){\r
6819         if (this.disabled) {\r
6820             return;\r
6821         }\r
6822         var middle = this.getMiddle();\r
6823         this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';\r
6824         this.trigger.addClass(this.tmpHoverClass);\r
6825     },\r
6826 \r
6827     //private\r
6828     onMouseOut: function(){\r
6829         this.trigger.removeClass(this.tmpHoverClass);\r
6830     },\r
6831 \r
6832     //private\r
6833     onMouseMove: function(){\r
6834         if (this.disabled) {\r
6835             return;\r
6836         }\r
6837         var middle = this.getMiddle();\r
6838         if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||\r
6839         ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {\r
6840         }\r
6841     },\r
6842 \r
6843     //private\r
6844     onMouseDown: function(){\r
6845         if (this.disabled) {\r
6846             return;\r
6847         }\r
6848         var middle = this.getMiddle();\r
6849         this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';\r
6850         this.trigger.addClass(this.tmpClickClass);\r
6851     },\r
6852 \r
6853     //private\r
6854     onMouseUp: function(){\r
6855         this.trigger.removeClass(this.tmpClickClass);\r
6856     },\r
6857 \r
6858     //private\r
6859     onTriggerClick: function(){\r
6860         if (this.disabled || this.el.dom.readOnly) {\r
6861             return;\r
6862         }\r
6863         var middle = this.getMiddle();\r
6864         var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';\r
6865         this['onSpin' + ud]();\r
6866     },\r
6867 \r
6868     //private\r
6869     getMiddle: function(){\r
6870         var t = this.trigger.getTop();\r
6871         var h = this.trigger.getHeight();\r
6872         var middle = t + (h / 2);\r
6873         return middle;\r
6874     },\r
6875 \r
6876     //private\r
6877     //checks if control is allowed to spin\r
6878     isSpinnable: function(){\r
6879         if (this.disabled || this.el.dom.readOnly) {\r
6880             Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly\r
6881             return false;\r
6882         }\r
6883         return true;\r
6884     },\r
6885 \r
6886     handleMouseWheel: function(e){\r
6887         //disable scrolling when not focused\r
6888         if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {\r
6889             return;\r
6890         }\r
6891 \r
6892         var delta = e.getWheelDelta();\r
6893         if (delta > 0) {\r
6894             this.onSpinUp();\r
6895             e.stopEvent();\r
6896         }\r
6897         else\r
6898             if (delta < 0) {\r
6899                 this.onSpinDown();\r
6900                 e.stopEvent();\r
6901             }\r
6902     },\r
6903 \r
6904     //private\r
6905     startDrag: function(){\r
6906         this.proxy.show();\r
6907         this._previousY = Ext.fly(this.dd.getDragEl()).getTop();\r
6908     },\r
6909 \r
6910     //private\r
6911     endDrag: function(){\r
6912         this.proxy.hide();\r
6913     },\r
6914 \r
6915     //private\r
6916     onDrag: function(){\r
6917         if (this.disabled) {\r
6918             return;\r
6919         }\r
6920         var y = Ext.fly(this.dd.getDragEl()).getTop();\r
6921         var ud = '';\r
6922 \r
6923         if (this._previousY > y) {\r
6924             ud = 'Up';\r
6925         } //up\r
6926         if (this._previousY < y) {\r
6927             ud = 'Down';\r
6928         } //down\r
6929         if (ud != '') {\r
6930             this['onSpin' + ud]();\r
6931         }\r
6932 \r
6933         this._previousY = y;\r
6934     },\r
6935 \r
6936     //private\r
6937     onSpinUp: function(){\r
6938         if (this.isSpinnable() == false) {\r
6939             return;\r
6940         }\r
6941         if (Ext.EventObject.shiftKey == true) {\r
6942             this.onSpinUpAlternate();\r
6943             return;\r
6944         }\r
6945         else {\r
6946             this.spin(false, false);\r
6947         }\r
6948         this.field.fireEvent("spin", this);\r
6949         this.field.fireEvent("spinup", this);\r
6950     },\r
6951 \r
6952     //private\r
6953     onSpinDown: function(){\r
6954         if (this.isSpinnable() == false) {\r
6955             return;\r
6956         }\r
6957         if (Ext.EventObject.shiftKey == true) {\r
6958             this.onSpinDownAlternate();\r
6959             return;\r
6960         }\r
6961         else {\r
6962             this.spin(true, false);\r
6963         }\r
6964         this.field.fireEvent("spin", this);\r
6965         this.field.fireEvent("spindown", this);\r
6966     },\r
6967 \r
6968     //private\r
6969     onSpinUpAlternate: function(){\r
6970         if (this.isSpinnable() == false) {\r
6971             return;\r
6972         }\r
6973         this.spin(false, true);\r
6974         this.field.fireEvent("spin", this);\r
6975         this.field.fireEvent("spinup", this);\r
6976     },\r
6977 \r
6978     //private\r
6979     onSpinDownAlternate: function(){\r
6980         if (this.isSpinnable() == false) {\r
6981             return;\r
6982         }\r
6983         this.spin(true, true);\r
6984         this.field.fireEvent("spin", this);\r
6985         this.field.fireEvent("spindown", this);\r
6986     },\r
6987 \r
6988     spin: function(down, alternate){\r
6989         var v = parseFloat(this.field.getValue());\r
6990         var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;\r
6991         (down == true) ? v -= incr : v += incr;\r
6992 \r
6993         v = (isNaN(v)) ? this.defaultValue : v;\r
6994         v = this.fixBoundries(v);\r
6995         this.field.setRawValue(v);\r
6996     },\r
6997 \r
6998     fixBoundries: function(value){\r
6999         var v = value;\r
7000 \r
7001         if (this.field.minValue != undefined && v < this.field.minValue) {\r
7002             v = this.field.minValue;\r
7003         }\r
7004         if (this.field.maxValue != undefined && v > this.field.maxValue) {\r
7005             v = this.field.maxValue;\r
7006         }\r
7007 \r
7008         return this.fixPrecision(v);\r
7009     },\r
7010 \r
7011     // private\r
7012     fixPrecision: function(value){\r
7013         var nan = isNaN(value);\r
7014         if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {\r
7015             return nan ? '' : value;\r
7016         }\r
7017         return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));\r
7018     },\r
7019 \r
7020     doDestroy: function(){\r
7021         if (this.trigger) {\r
7022             this.trigger.remove();\r
7023         }\r
7024         if (this.wrap) {\r
7025             this.wrap.remove();\r
7026             delete this.field.wrap;\r
7027         }\r
7028 \r
7029         if (this.splitter) {\r
7030             this.splitter.remove();\r
7031         }\r
7032 \r
7033         if (this.dd) {\r
7034             this.dd.unreg();\r
7035             this.dd = null;\r
7036         }\r
7037 \r
7038         if (this.proxy) {\r
7039             this.proxy.remove();\r
7040         }\r
7041 \r
7042         if (this.repeater) {\r
7043             this.repeater.purgeListeners();\r
7044         }\r
7045     }\r
7046 });\r
7047 \r
7048 //backwards compat\r
7049 Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){\r
7050     Ext.apply(this, config);\r
7051 }\r
7052 Ext.ux.Spotlight.prototype = {\r
7053     active : false,\r
7054     animate : true,\r
7055     duration: .25,\r
7056     easing:'easeNone',\r
7057 \r
7058     // private\r
7059     animated : false,\r
7060 \r
7061     createElements : function(){\r
7062         var bd = Ext.getBody();\r
7063 \r
7064         this.right = bd.createChild({cls:'x-spotlight'});\r
7065         this.left = bd.createChild({cls:'x-spotlight'});\r
7066         this.top = bd.createChild({cls:'x-spotlight'});\r
7067         this.bottom = bd.createChild({cls:'x-spotlight'});\r
7068 \r
7069         this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);\r
7070     },\r
7071 \r
7072     show : function(el, callback, scope){\r
7073         if(this.animated){\r
7074             this.show.defer(50, this, [el, callback, scope]);\r
7075             return;\r
7076         }\r
7077         this.el = Ext.get(el);\r
7078         if(!this.right){\r
7079             this.createElements();\r
7080         }\r
7081         if(!this.active){\r
7082             this.all.setDisplayed('');\r
7083             this.applyBounds(true, false);\r
7084             this.active = true;\r
7085             Ext.EventManager.onWindowResize(this.syncSize, this);\r
7086             this.applyBounds(false, this.animate, false, callback, scope);\r
7087         }else{\r
7088             this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous\r
7089         }\r
7090     },\r
7091 \r
7092     hide : function(callback, scope){\r
7093         if(this.animated){\r
7094             this.hide.defer(50, this, [callback, scope]);\r
7095             return;\r
7096         }\r
7097         Ext.EventManager.removeResizeListener(this.syncSize, this);\r
7098         this.applyBounds(true, this.animate, true, callback, scope);\r
7099     },\r
7100 \r
7101     doHide : function(){\r
7102         this.active = false;\r
7103         this.all.setDisplayed(false);\r
7104     },\r
7105 \r
7106     syncSize : function(){\r
7107         this.applyBounds(false, false);\r
7108     },\r
7109 \r
7110     applyBounds : function(basePts, anim, doHide, callback, scope){\r
7111 \r
7112         var rg = this.el.getRegion();\r
7113 \r
7114         var dw = Ext.lib.Dom.getViewWidth(true);\r
7115         var dh = Ext.lib.Dom.getViewHeight(true);\r
7116 \r
7117         var c = 0, cb = false;\r
7118         if(anim){\r
7119             cb = {\r
7120                 callback: function(){\r
7121                     c++;\r
7122                     if(c == 4){\r
7123                         this.animated = false;\r
7124                         if(doHide){\r
7125                             this.doHide();\r
7126                         }\r
7127                         Ext.callback(callback, scope, [this]);\r
7128                     }\r
7129                 },\r
7130                 scope: this,\r
7131                 duration: this.duration,\r
7132                 easing: this.easing\r
7133             };\r
7134             this.animated = true;\r
7135         }\r
7136 \r
7137         this.right.setBounds(\r
7138                 rg.right,\r
7139                 basePts ? dh : rg.top,\r
7140                 dw - rg.right,\r
7141                 basePts ? 0 : (dh - rg.top),\r
7142                 cb);\r
7143 \r
7144         this.left.setBounds(\r
7145                 0,\r
7146                 0,\r
7147                 rg.left,\r
7148                 basePts ? 0 : rg.bottom,\r
7149                 cb);\r
7150 \r
7151         this.top.setBounds(\r
7152                 basePts ? dw : rg.left,\r
7153                 0,\r
7154                 basePts ? 0 : dw - rg.left,\r
7155                 rg.top,\r
7156                 cb);\r
7157 \r
7158         this.bottom.setBounds(\r
7159                 0,\r
7160                 rg.bottom,\r
7161                 basePts ? 0 : rg.right,\r
7162                 dh - rg.bottom,\r
7163                 cb);\r
7164 \r
7165         if(!anim){\r
7166             if(doHide){\r
7167                 this.doHide();\r
7168             }\r
7169             if(callback){\r
7170                 Ext.callback(callback, scope, [this]);\r
7171             }\r
7172         }\r
7173     },\r
7174 \r
7175     destroy : function(){\r
7176         this.doHide();\r
7177         Ext.destroy(\r
7178             this.right,\r
7179             this.left,\r
7180             this.top,\r
7181             this.bottom);\r
7182         delete this.el;\r
7183         delete this.all;\r
7184     }\r
7185 };\r
7186 \r
7187 //backwards compat\r
7188 Ext.Spotlight = Ext.ux.Spotlight;/**
7189  * @class Ext.ux.StatusBar
7190  * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}.  In addition to
7191  * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
7192  * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
7193  * status text and icon.  You can also indicate that something is processing using the {@link #showBusy} method.</p>
7194  * <pre><code>
7195 new Ext.Panel({
7196     title: 'StatusBar',
7197     // etc.
7198     bbar: new Ext.ux.StatusBar({
7199         id: 'my-status',
7200
7201         // defaults to use when the status is cleared:
7202         defaultText: 'Default status text',
7203         defaultIconCls: 'default-icon',
7204
7205         // values to set initially:
7206         text: 'Ready',
7207         iconCls: 'ready-icon',
7208
7209         // any standard Toolbar items:
7210         items: [{
7211             text: 'A Button'
7212         }, '-', 'Plain Text']
7213     })
7214 });
7215
7216 // Update the status bar later in code:
7217 var sb = Ext.getCmp('my-status');
7218 sb.setStatus({
7219     text: 'OK',
7220     iconCls: 'ok-icon',
7221     clear: true // auto-clear after a set interval
7222 });
7223
7224 // Set the status bar to show that something is processing:
7225 sb.showBusy();
7226
7227 // processing....
7228
7229 sb.clearStatus(); // once completeed
7230 </code></pre>
7231  * @extends Ext.Toolbar
7232  * @constructor
7233  * Creates a new StatusBar
7234  * @param {Object/Array} config A config object
7235  */
7236 Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
7237     /**
7238      * @cfg {String} statusAlign
7239      * The alignment of the status element within the overall StatusBar layout.  When the StatusBar is rendered,
7240      * it creates an internal div containing the status text and icon.  Any additional Toolbar items added in the
7241      * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
7242      * rendered, in added order, to the opposite side.  The status element is greedy, so it will automatically
7243      * expand to take up all sapce left over by any other items.  Example usage:
7244      * <pre><code>
7245 // Create a left-aligned status bar containing a button,
7246 // separator and text item that will be right-aligned (default):
7247 new Ext.Panel({
7248     title: 'StatusBar',
7249     // etc.
7250     bbar: new Ext.ux.StatusBar({
7251         defaultText: 'Default status text',
7252         id: 'status-id',
7253         items: [{
7254             text: 'A Button'
7255         }, '-', 'Plain Text']
7256     })
7257 });
7258
7259 // By adding the statusAlign config, this will create the
7260 // exact same toolbar, except the status and toolbar item
7261 // layout will be reversed from the previous example:
7262 new Ext.Panel({
7263     title: 'StatusBar',
7264     // etc.
7265     bbar: new Ext.ux.StatusBar({
7266         defaultText: 'Default status text',
7267         id: 'status-id',
7268         statusAlign: 'right',
7269         items: [{
7270             text: 'A Button'
7271         }, '-', 'Plain Text']
7272     })
7273 });
7274 </code></pre>
7275      */
7276     /**
7277      * @cfg {String} defaultText
7278      * The default {@link #text} value.  This will be used anytime the status bar is cleared with the
7279      * <tt>useDefaults:true</tt> option (defaults to '').
7280      */
7281     /**
7282      * @cfg {String} defaultIconCls
7283      * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
7284      * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
7285      */
7286     /**
7287      * @cfg {String} text
7288      * A string that will be <b>initially</b> set as the status message.  This string
7289      * will be set as innerHTML (html tags are accepted) for the toolbar item.
7290      * If not specified, the value set for <code>{@link #defaultText}</code>
7291      * will be used.
7292      */
7293     /**
7294      * @cfg {String} iconCls
7295      * A CSS class that will be <b>initially</b> set as the status bar icon and is
7296      * expected to provide a background image (defaults to '').
7297      * Example usage:<pre><code>
7298 // Example CSS rule:
7299 .x-statusbar .x-status-custom {
7300     padding-left: 25px;
7301     background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
7302 }
7303
7304 // Setting a default icon:
7305 var sb = new Ext.ux.StatusBar({
7306     defaultIconCls: 'x-status-custom'
7307 });
7308
7309 // Changing the icon:
7310 sb.setStatus({
7311     text: 'New status',
7312     iconCls: 'x-status-custom'
7313 });
7314 </code></pre>
7315      */
7316
7317     /**
7318      * @cfg {String} cls
7319      * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
7320      */
7321     cls : 'x-statusbar',
7322     /**
7323      * @cfg {String} busyIconCls
7324      * The default <code>{@link #iconCls}</code> applied when calling
7325      * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
7326      * It can be overridden at any time by passing the <code>iconCls</code>
7327      * argument into <code>{@link #showBusy}</code>.
7328      */
7329     busyIconCls : 'x-status-busy',
7330     /**
7331      * @cfg {String} busyText
7332      * The default <code>{@link #text}</code> applied when calling
7333      * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
7334      * It can be overridden at any time by passing the <code>text</code>
7335      * argument into <code>{@link #showBusy}</code>.
7336      */
7337     busyText : 'Loading...',
7338     /**
7339      * @cfg {Number} autoClear
7340      * The number of milliseconds to wait after setting the status via
7341      * <code>{@link #setStatus}</code> before automatically clearing the status
7342      * text and icon (defaults to <tt>5000</tt>).  Note that this only applies
7343      * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
7344      * since that is the only way to defer clearing the status.  This can
7345      * be overridden by specifying a different <tt>wait</tt> value in
7346      * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
7347      * always clear the status bar immediately and ignore this value.
7348      */
7349     autoClear : 5000,
7350
7351     /**
7352      * @cfg {String} emptyText
7353      * The text string to use if no text has been set.  Defaults to
7354      * <tt>'&nbsp;'</tt>).  If there are no other items in the toolbar using
7355      * an empty string (<tt>''</tt>) for this value would end up in the toolbar
7356      * height collapsing since the empty string will not maintain the toolbar
7357      * height.  Use <tt>''</tt> if the toolbar should collapse in height
7358      * vertically when no text is specified and there are no other items in
7359      * the toolbar.
7360      */
7361     emptyText : '&nbsp;',
7362
7363     // private
7364     activeThreadId : 0,
7365
7366     // private
7367     initComponent : function(){
7368         if(this.statusAlign=='right'){
7369             this.cls += ' x-status-right';
7370         }
7371         Ext.ux.StatusBar.superclass.initComponent.call(this);
7372     },
7373
7374     // private
7375     afterRender : function(){
7376         Ext.ux.StatusBar.superclass.afterRender.call(this);
7377
7378         var right = this.statusAlign == 'right';
7379         this.currIconCls = this.iconCls || this.defaultIconCls;
7380         this.statusEl = new Ext.Toolbar.TextItem({
7381             cls: 'x-status-text ' + (this.currIconCls || ''),
7382             text: this.text || this.defaultText || ''
7383         });
7384
7385         if(right){
7386             this.add('->');
7387             this.add(this.statusEl);
7388         }else{
7389             this.insert(0, this.statusEl);
7390             this.insert(1, '->');
7391         }
7392
7393 //         this.statusEl = td.createChild({
7394 //             cls: 'x-status-text ' + (this.iconCls || this.defaultIconCls || ''),
7395 //             html: this.text || this.defaultText || ''
7396 //         });
7397 //         this.statusEl.unselectable();
7398
7399 //         this.spacerEl = td.insertSibling({
7400 //             tag: 'td',
7401 //             style: 'width:100%',
7402 //             cn: [{cls:'ytb-spacer'}]
7403 //         }, right ? 'before' : 'after');
7404     },
7405
7406     /**
7407      * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
7408      * status that was set after a specified interval.
7409      * @param {Object/String} config A config object specifying what status to set, or a string assumed
7410      * to be the status text (and all other options are defaulted as explained below). A config
7411      * object containing any or all of the following properties can be passed:<ul>
7412      * <li><tt>text</tt> {String} : (optional) The status text to display.  If not specified, any current
7413      * status text will remain unchanged.</li>
7414      * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
7415      * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
7416      * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
7417      * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
7418      * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
7419      * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
7420      * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
7421      * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
7422      * All other options will be defaulted as with the boolean option.  To customize any other options,
7423      * you can pass an object in the format:<ul>
7424      *    <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
7425      *    (defaults to {@link #autoClear}).</li>
7426      *    <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
7427      *    executes (defaults to true which fades the status out).</li>
7428      *    <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
7429      *    (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
7430      * </ul></li></ul>
7431      * Example usage:<pre><code>
7432 // Simple call to update the text
7433 statusBar.setStatus('New status');
7434
7435 // Set the status and icon, auto-clearing with default options:
7436 statusBar.setStatus({
7437     text: 'New status',
7438     iconCls: 'x-status-custom',
7439     clear: true
7440 });
7441
7442 // Auto-clear with custom options:
7443 statusBar.setStatus({
7444     text: 'New status',
7445     iconCls: 'x-status-custom',
7446     clear: {
7447         wait: 8000,
7448         anim: false,
7449         useDefaults: false
7450     }
7451 });
7452 </code></pre>
7453      * @return {Ext.ux.StatusBar} this
7454      */
7455     setStatus : function(o){
7456         o = o || {};
7457
7458         if(typeof o == 'string'){
7459             o = {text:o};
7460         }
7461         if(o.text !== undefined){
7462             this.setText(o.text);
7463         }
7464         if(o.iconCls !== undefined){
7465             this.setIcon(o.iconCls);
7466         }
7467
7468         if(o.clear){
7469             var c = o.clear,
7470                 wait = this.autoClear,
7471                 defaults = {useDefaults: true, anim: true};
7472
7473             if(typeof c == 'object'){
7474                 c = Ext.applyIf(c, defaults);
7475                 if(c.wait){
7476                     wait = c.wait;
7477                 }
7478             }else if(typeof c == 'number'){
7479                 wait = c;
7480                 c = defaults;
7481             }else if(typeof c == 'boolean'){
7482                 c = defaults;
7483             }
7484
7485             c.threadId = this.activeThreadId;
7486             this.clearStatus.defer(wait, this, [c]);
7487         }
7488         return this;
7489     },
7490
7491     /**
7492      * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
7493      * @param {Object} config (optional) A config object containing any or all of the following properties.  If this
7494      * object is not specified the status will be cleared using the defaults below:<ul>
7495      * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
7496      * to false which clears immediately).</li>
7497      * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
7498      * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
7499      * </ul>
7500      * @return {Ext.ux.StatusBar} this
7501      */
7502     clearStatus : function(o){
7503         o = o || {};
7504
7505         if(o.threadId && o.threadId !== this.activeThreadId){
7506             // this means the current call was made internally, but a newer
7507             // thread has set a message since this call was deferred.  Since
7508             // we don't want to overwrite a newer message just ignore.
7509             return this;
7510         }
7511
7512         var text = o.useDefaults ? this.defaultText : this.emptyText,
7513             iconCls = o.useDefaults ? (this.defaultIconCls ? this.defaultIconCls : '') : '';
7514
7515         if(o.anim){
7516             // animate the statusEl Ext.Element
7517             this.statusEl.el.fadeOut({
7518                 remove: false,
7519                 useDisplay: true,
7520                 scope: this,
7521                 callback: function(){
7522                     this.setStatus({
7523                             text: text,
7524                             iconCls: iconCls
7525                         });
7526
7527                     this.statusEl.el.show();
7528                 }
7529             });
7530         }else{
7531             // hide/show the el to avoid jumpy text or icon
7532             this.statusEl.hide();
7533                 this.setStatus({
7534                     text: text,
7535                     iconCls: iconCls
7536                 });
7537             this.statusEl.show();
7538         }
7539         return this;
7540     },
7541
7542     /**
7543      * Convenience method for setting the status text directly.  For more flexible options see {@link #setStatus}.
7544      * @param {String} text (optional) The text to set (defaults to '')
7545      * @return {Ext.ux.StatusBar} this
7546      */
7547     setText : function(text){
7548         this.activeThreadId++;
7549         this.text = text || '';
7550         if(this.rendered){
7551             this.statusEl.setText(this.text);
7552         }
7553         return this;
7554     },
7555
7556     /**
7557      * Returns the current status text.
7558      * @return {String} The status text
7559      */
7560     getText : function(){
7561         return this.text;
7562     },
7563
7564     /**
7565      * Convenience method for setting the status icon directly.  For more flexible options see {@link #setStatus}.
7566      * See {@link #iconCls} for complete details about customizing the icon.
7567      * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
7568      * @return {Ext.ux.StatusBar} this
7569      */
7570     setIcon : function(cls){
7571         this.activeThreadId++;
7572         cls = cls || '';
7573
7574         if(this.rendered){
7575                 if(this.currIconCls){
7576                     this.statusEl.removeClass(this.currIconCls);
7577                     this.currIconCls = null;
7578                 }
7579                 if(cls.length > 0){
7580                     this.statusEl.addClass(cls);
7581                     this.currIconCls = cls;
7582                 }
7583         }else{
7584             this.currIconCls = cls;
7585         }
7586         return this;
7587     },
7588
7589     /**
7590      * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
7591      * a "busy" state, usually for loading or processing activities.
7592      * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
7593      * string to use as the status text (in which case all other options for setStatus will be defaulted).  Use the
7594      * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
7595      * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
7596      * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
7597      * @return {Ext.ux.StatusBar} this
7598      */
7599     showBusy : function(o){
7600         if(typeof o == 'string'){
7601             o = {text:o};
7602         }
7603         o = Ext.applyIf(o || {}, {
7604             text: this.busyText,
7605             iconCls: this.busyIconCls
7606         });
7607         return this.setStatus(o);
7608     }
7609 });
7610 Ext.reg('statusbar', Ext.ux.StatusBar);
7611 /**\r
7612  * @class Ext.ux.TabCloseMenu\r
7613  * @extends Object \r
7614  * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.\r
7615  * \r
7616  * @ptype tabclosemenu\r
7617  */\r
7618 Ext.ux.TabCloseMenu = function(){\r
7619     var tabs, menu, ctxItem;\r
7620     this.init = function(tp){\r
7621         tabs = tp;\r
7622         tabs.on('contextmenu', onContextMenu);\r
7623     };\r
7624 \r
7625     function onContextMenu(ts, item, e){\r
7626         if(!menu){ // create context menu on first right click\r
7627             menu = new Ext.menu.Menu({            \r
7628             items: [{\r
7629                 id: tabs.id + '-close',\r
7630                 text: 'Close Tab',\r
7631                 handler : function(){\r
7632                     tabs.remove(ctxItem);\r
7633                 }\r
7634             },{\r
7635                 id: tabs.id + '-close-others',\r
7636                 text: 'Close Other Tabs',\r
7637                 handler : function(){\r
7638                     tabs.items.each(function(item){\r
7639                         if(item.closable && item != ctxItem){\r
7640                             tabs.remove(item);\r
7641                         }\r
7642                     });\r
7643                 }\r
7644             }]});\r
7645         }\r
7646         ctxItem = item;\r
7647         var items = menu.items;\r
7648         items.get(tabs.id + '-close').setDisabled(!item.closable);\r
7649         var disableOthers = true;\r
7650         tabs.items.each(function(){\r
7651             if(this != item && this.closable){\r
7652                 disableOthers = false;\r
7653                 return false;\r
7654             }\r
7655         });\r
7656         items.get(tabs.id + '-close-others').setDisabled(disableOthers);\r
7657         e.stopEvent();\r
7658         menu.showAt(e.getPoint());\r
7659     }\r
7660 };\r
7661 \r
7662 Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);\r
7663 Ext.ns('Ext.ux.grid');
7664
7665 /**
7666  * @class Ext.ux.grid.TableGrid
7667  * @extends Ext.grid.GridPanel
7668  * A Grid which creates itself from an existing HTML table element.
7669  * @history
7670  * 2007-03-01 Original version by Nige "Animal" White
7671  * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor
7672  * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
7673  * The table MUST have some type of size defined for the grid to fill. The container will be
7674  * automatically set to position relative if it isn't already.
7675  * @param {Object} config A config object that sets properties on this grid and has two additional (optional)
7676  * properties: fields and columns which allow for customizing data fields and columns for this grid.
7677  */
7678 Ext.ux.grid.TableGrid = function(table, config){
7679     config = config ||
7680     {};
7681     Ext.apply(this, config);
7682     var cf = config.fields || [], ch = config.columns || [];
7683     table = Ext.get(table);
7684     
7685     var ct = table.insertSibling();
7686     
7687     var fields = [], cols = [];
7688     var headers = table.query("thead th");
7689     for (var i = 0, h; h = headers[i]; i++) {
7690         var text = h.innerHTML;
7691         var name = 'tcol-' + i;
7692         
7693         fields.push(Ext.applyIf(cf[i] ||
7694         {}, {
7695             name: name,
7696             mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
7697         }));
7698         
7699         cols.push(Ext.applyIf(ch[i] ||
7700         {}, {
7701             'header': text,
7702             'dataIndex': name,
7703             'width': h.offsetWidth,
7704             'tooltip': h.title,
7705             'sortable': true
7706         }));
7707     }
7708     
7709     var ds = new Ext.data.Store({
7710         reader: new Ext.data.XmlReader({
7711             record: 'tbody tr'
7712         }, fields)
7713     });
7714     
7715     ds.loadData(table.dom);
7716     
7717     var cm = new Ext.grid.ColumnModel(cols);
7718     
7719     if (config.width || config.height) {
7720         ct.setSize(config.width || 'auto', config.height || 'auto');
7721     }
7722     else {
7723         ct.setWidth(table.getWidth());
7724     }
7725     
7726     if (config.remove !== false) {
7727         table.remove();
7728     }
7729     
7730     Ext.applyIf(this, {
7731         'ds': ds,
7732         'cm': cm,
7733         'sm': new Ext.grid.RowSelectionModel(),
7734         autoHeight: true,
7735         autoWidth: false
7736     });
7737     Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {});
7738 };
7739
7740 Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
7741
7742 //backwards compat
7743 Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
7744
7745
7746 Ext.ux.TabScrollerMenu =  Ext.extend(Object, {
7747         pageSize       : 10,
7748         maxText        : 15,
7749         menuPrefixText : 'Items',
7750         constructor    : function(config) {
7751                 config = config || {};
7752                 Ext.apply(this, config);
7753         },
7754         init : function(tabPanel) {
7755                 Ext.apply(tabPanel, this.tabPanelMethods);
7756                 
7757                 tabPanel.tabScrollerMenu = this;
7758                 var thisRef = this;
7759                 
7760                 tabPanel.on({
7761                         render : {
7762                                 scope  : tabPanel,
7763                                 single : true,
7764                                 fn     : function() { 
7765                                         var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
7766                                         tabPanel.createScrollers = newFn;
7767                                 }
7768                         }
7769                 });
7770         },
7771         // private && sequeneced
7772         createPanelsMenu : function() {
7773                 var h = this.stripWrap.dom.offsetHeight;
7774                 
7775                 //move the right menu item to the left 18px
7776                 var rtScrBtn = this.header.dom.firstChild;
7777                 Ext.fly(rtScrBtn).applyStyles({
7778                         right : '18px'
7779                 });
7780                 
7781                 var stripWrap = Ext.get(this.strip.dom.parentNode);
7782                 stripWrap.applyStyles({
7783                          'margin-right' : '36px'
7784                 });
7785                 
7786                 // Add the new righthand menu
7787                 var scrollMenu = this.header.insertFirst({
7788                         cls:'x-tab-tabmenu-right'
7789                 });
7790                 scrollMenu.setHeight(h);
7791                 scrollMenu.addClassOnOver('x-tab-tabmenu-over');
7792                 scrollMenu.on('click', this.showTabsMenu, this);        
7793                 
7794                 this.scrollLeft.show = this.scrollLeft.show.createSequence(function() {
7795                         scrollMenu.show();                                                                                                                                               
7796                 });
7797                 
7798                 this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() {
7799                         scrollMenu.hide();                                                              
7800                 });
7801                 
7802         },
7803         // public
7804         getPageSize : function() {
7805                 return this.pageSize;
7806         },
7807         // public
7808         setPageSize : function(pageSize) {
7809                 this.pageSize = pageSize;
7810         },
7811         // public
7812         getMaxText : function() {
7813                 return this.maxText;
7814         },
7815         // public
7816         setMaxText : function(t) {
7817                 this.maxText = t;
7818         },
7819         getMenuPrefixText : function() {
7820                 return this.menuPrefixText;
7821         },
7822         setMenuPrefixText : function(t) {
7823                 this.menuPrefixText = t;
7824         },
7825         // private && applied to the tab panel itself.
7826         tabPanelMethods : {
7827                 // all execute within the scope of the tab panel
7828                 // private      
7829                 showTabsMenu : function(e) {            
7830                         if (! this.tabsMenu) {
7831                                 this.tabsMenu =  new Ext.menu.Menu();
7832                                 this.on('beforedestroy', this.tabsMenu.destroy, this.tabsMenu);
7833                         }
7834                         
7835                         this.tabsMenu.removeAll();
7836                         
7837                         this.generateTabMenuItems();
7838                         
7839                         var target = Ext.get(e.getTarget());
7840                         var xy     = target.getXY();
7841                         
7842                         //Y param + 24 pixels
7843                         xy[1] += 24;
7844                         
7845                         this.tabsMenu.showAt(xy);
7846                 },
7847                 // private      
7848                 generateTabMenuItems : function() {
7849                         var curActive  = this.getActiveTab();
7850                         var totalItems = this.items.getCount();
7851                         var pageSize   = this.tabScrollerMenu.getPageSize();
7852                         
7853                         
7854                         if (totalItems > pageSize)  {
7855                                 var numSubMenus = Math.floor(totalItems / pageSize);
7856                                 var remainder   = totalItems % pageSize;
7857                                 
7858                                 // Loop through all of the items and create submenus in chunks of 10
7859                                 for (var i = 0 ; i < numSubMenus; i++) {
7860                                         var curPage = (i + 1) * pageSize;
7861                                         var menuItems = [];
7862                                         
7863                                         
7864                                         for (var x = 0; x < pageSize; x++) {                            
7865                                                 index = x + curPage - pageSize;
7866                                                 var item = this.items.get(index);
7867                                                 menuItems.push(this.autoGenMenuItem(item));
7868                                         }
7869                                         
7870                                         this.tabsMenu.add({
7871                                                 text : this.tabScrollerMenu.getMenuPrefixText() + ' '  + (curPage - pageSize + 1) + ' - ' + curPage,
7872                                                 menu : menuItems
7873                                         });
7874                                         
7875                                 }
7876                                 // remaining items
7877                                 if (remainder > 0) {
7878                                         var start = numSubMenus * pageSize;
7879                                         menuItems = [];
7880                                         for (var i = start ; i < totalItems; i ++ ) {                                   
7881                                                 var item = this.items.get(i);
7882                                                 menuItems.push(this.autoGenMenuItem(item));
7883                                         }
7884                                         
7885                                         
7886                                         this.tabsMenu.add({
7887                                                 text : this.tabScrollerMenu.menuPrefixText  + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
7888                                                 menu : menuItems
7889                                         });
7890                                         
7891
7892                                 }
7893                         }
7894                         else {
7895                                 this.items.each(function(item) {
7896                                         if (item.id != curActive.id && ! item.hidden) {
7897                                                 menuItems.push(this.autoGenMenuItem(item));
7898                                         }
7899                                 }, this);
7900                         }       
7901                 },
7902                 // private
7903                 autoGenMenuItem : function(item) {
7904                         var maxText = this.tabScrollerMenu.getMaxText();
7905                         var text    = Ext.util.Format.ellipsis(item.title, maxText);
7906                         
7907                         return {
7908                                 text      : text,
7909                                 handler   : this.showTabFromMenu,
7910                                 scope     : this,
7911                                 disabled  : item.disabled,
7912                                 tabToShow : item,
7913                                 iconCls   : item.iconCls
7914                         }
7915                 
7916                 },
7917                 // private
7918                 showTabFromMenu : function(menuItem) {
7919                         this.setActiveTab(menuItem.tabToShow);
7920                 }       
7921         }       
7922 });
7923 Ext.ns('Ext.ux.tree');
7924
7925 /**
7926  * @class Ext.ux.tree.XmlTreeLoader
7927  * @extends Ext.tree.TreeLoader
7928  * <p>A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s.
7929  * Any text value included as a text node in the XML will be added to the parent node as an attribute
7930  * called <tt>innerText</tt>.  Also, the tag name of each XML node will be added to the tree node as
7931  * an attribute called <tt>tagName</tt>.</p>
7932  * <p>By default, this class expects that your source XML will provide the necessary attributes on each
7933  * node as expected by the {@link Ext.tree.TreePanel} to display and load properly.  However, you can
7934  * provide your own custom processing of node attributes by overriding the {@link #processNode} method
7935  * and modifying the attributes as needed before they are used to create the associated TreeNode.</p>
7936  * @constructor
7937  * Creates a new XmlTreeloader.
7938  * @param {Object} config A config object containing config properties.
7939  */
7940 Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
7941     /**
7942      * @property  XML_NODE_ELEMENT
7943      * XML element node (value 1, read-only)
7944      * @type Number
7945      */
7946     XML_NODE_ELEMENT : 1,
7947     /**
7948      * @property  XML_NODE_TEXT
7949      * XML text node (value 3, read-only)
7950      * @type Number
7951      */
7952     XML_NODE_TEXT : 3,
7953
7954     // private override
7955     processResponse : function(response, node, callback){
7956         var xmlData = response.responseXML;
7957         var root = xmlData.documentElement || xmlData;
7958
7959         try{
7960             node.beginUpdate();
7961             node.appendChild(this.parseXml(root));
7962             node.endUpdate();
7963
7964             if(typeof callback == "function"){
7965                 callback(this, node);
7966             }
7967         }catch(e){
7968             this.handleFailure(response);
7969         }
7970     },
7971
7972     // private
7973     parseXml : function(node) {
7974         var nodes = [];
7975         Ext.each(node.childNodes, function(n){
7976             if(n.nodeType == this.XML_NODE_ELEMENT){
7977                 var treeNode = this.createNode(n);
7978                 if(n.childNodes.length > 0){
7979                     var child = this.parseXml(n);
7980                     if(typeof child == 'string'){
7981                         treeNode.attributes.innerText = child;
7982                     }else{
7983                         treeNode.appendChild(child);
7984                     }
7985                 }
7986                 nodes.push(treeNode);
7987             }
7988             else if(n.nodeType == this.XML_NODE_TEXT){
7989                 var text = n.nodeValue.trim();
7990                 if(text.length > 0){
7991                     return nodes = text;
7992                 }
7993             }
7994         }, this);
7995
7996         return nodes;
7997     },
7998
7999     // private override
8000     createNode : function(node){
8001         var attr = {
8002             tagName: node.tagName
8003         };
8004
8005         Ext.each(node.attributes, function(a){
8006             attr[a.nodeName] = a.nodeValue;
8007         });
8008
8009         this.processAttributes(attr);
8010
8011         return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
8012     },
8013
8014     /*
8015      * Template method intended to be overridden by subclasses that need to provide
8016      * custom attribute processing prior to the creation of each TreeNode.  This method
8017      * will be passed a config object containing existing TreeNode attribute name/value
8018      * pairs which can be modified as needed directly (no need to return the object).
8019      */
8020     processAttributes: Ext.emptyFn
8021 });
8022
8023 //backwards compat
8024 Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;
8025 /**
8026  * @class Ext.ux.ValidationStatus
8027  * A {@link Ext.StatusBar} plugin that provides automatic error notification when the
8028  * associated form contains validation errors.
8029  * @extends Ext.Component
8030  * @constructor
8031  * Creates a new ValiationStatus plugin
8032  * @param {Object} config A config object
8033  */
8034 Ext.ux.ValidationStatus = Ext.extend(Ext.Component, {
8035     /**
8036      * @cfg {String} errorIconCls
8037      * The {@link #iconCls} value to be applied to the status message when there is a
8038      * validation error. Defaults to <tt>'x-status-error'</tt>.
8039      */
8040     errorIconCls : 'x-status-error',
8041     /**
8042      * @cfg {String} errorListCls
8043      * The css class to be used for the error list when there are validation errors.
8044      * Defaults to <tt>'x-status-error-list'</tt>.
8045      */
8046     errorListCls : 'x-status-error-list',
8047     /**
8048      * @cfg {String} validIconCls
8049      * The {@link #iconCls} value to be applied to the status message when the form
8050      * validates. Defaults to <tt>'x-status-valid'</tt>.
8051      */
8052     validIconCls : 'x-status-valid',
8053     
8054     /**
8055      * @cfg {String} showText
8056      * The {@link #text} value to be applied when there is a form validation error.
8057      * Defaults to <tt>'The form has errors (click for details...)'</tt>.
8058      */
8059     showText : 'The form has errors (click for details...)',
8060     /**
8061      * @cfg {String} showText
8062      * The {@link #text} value to display when the error list is displayed.
8063      * Defaults to <tt>'Click again to hide the error list'</tt>.
8064      */
8065     hideText : 'Click again to hide the error list',
8066     /**
8067      * @cfg {String} submitText
8068      * The {@link #text} value to be applied when the form is being submitted.
8069      * Defaults to <tt>'Saving...'</tt>.
8070      */
8071     submitText : 'Saving...',
8072     
8073     // private
8074     init : function(sb){
8075         sb.on('render', function(){
8076             this.statusBar = sb;
8077             this.monitor = true;
8078             this.errors = new Ext.util.MixedCollection();
8079             this.listAlign = (sb.statusAlign=='right' ? 'br-tr?' : 'bl-tl?');
8080             
8081             if(this.form){
8082                 this.form = Ext.getCmp(this.form).getForm();
8083                 this.startMonitoring();
8084                 this.form.on('beforeaction', function(f, action){
8085                     if(action.type == 'submit'){
8086                         // Ignore monitoring while submitting otherwise the field validation
8087                         // events cause the status message to reset too early
8088                         this.monitor = false;
8089                     }
8090                 }, this);
8091                 var startMonitor = function(){
8092                     this.monitor = true;
8093                 };
8094                 this.form.on('actioncomplete', startMonitor, this);
8095                 this.form.on('actionfailed', startMonitor, this);
8096             }
8097         }, this, {single:true});
8098         sb.on({
8099             scope: this,
8100             afterlayout:{
8101                 single: true,
8102                 fn: function(){
8103                     // Grab the statusEl after the first layout.
8104                     sb.statusEl.getEl().on('click', this.onStatusClick, this, {buffer:200});
8105                 } 
8106             }, 
8107             beforedestroy:{
8108                 single: true,
8109                 fn: this.onDestroy
8110             } 
8111         });
8112     },
8113     
8114     // private
8115     startMonitoring : function(){
8116         this.form.items.each(function(f){
8117             f.on('invalid', this.onFieldValidation, this);
8118             f.on('valid', this.onFieldValidation, this);
8119         }, this);
8120     },
8121     
8122     // private
8123     stopMonitoring : function(){
8124         this.form.items.each(function(f){
8125             f.un('invalid', this.onFieldValidation, this);
8126             f.un('valid', this.onFieldValidation, this);
8127         }, this);
8128     },
8129     
8130     // private
8131     onDestroy : function(){
8132         this.stopMonitoring();
8133         this.statusBar.statusEl.un('click', this.onStatusClick, this);
8134         Ext.ux.ValidationStatus.superclass.onDestroy.call(this);
8135     },
8136     
8137     // private
8138     onFieldValidation : function(f, msg){
8139         if(!this.monitor){
8140             return false;
8141         }
8142         if(msg){
8143             this.errors.add(f.id, {field:f, msg:msg});
8144         }else{
8145             this.errors.removeKey(f.id);
8146         }
8147         this.updateErrorList();
8148         if(this.errors.getCount() > 0){
8149             if(this.statusBar.getText() != this.showText){
8150                 this.statusBar.setStatus({text:this.showText, iconCls:this.errorIconCls});
8151             }
8152         }else{
8153             this.statusBar.clearStatus().setIcon(this.validIconCls);
8154         }
8155     },
8156     
8157     // private
8158     updateErrorList : function(){
8159         if(this.errors.getCount() > 0){
8160                 var msg = '<ul>';
8161                 this.errors.each(function(err){
8162                     msg += ('<li id="x-err-'+ err.field.id +'"><a href="#">' + err.msg + '</a></li>');
8163                 }, this);
8164                 this.getMsgEl().update(msg+'</ul>');
8165         }else{
8166             this.getMsgEl().update('');
8167         }
8168     },
8169     
8170     // private
8171     getMsgEl : function(){
8172         if(!this.msgEl){
8173             this.msgEl = Ext.DomHelper.append(Ext.getBody(), {
8174                 cls: this.errorListCls+' x-hide-offsets'
8175             }, true);
8176             
8177             this.msgEl.on('click', function(e){
8178                 var t = e.getTarget('li', 10, true);
8179                 if(t){
8180                     Ext.getCmp(t.id.split('x-err-')[1]).focus();
8181                     this.hideErrors();
8182                 }
8183             }, this, {stopEvent:true}); // prevent anchor click navigation
8184         }
8185         return this.msgEl;
8186     },
8187     
8188     // private
8189     showErrors : function(){
8190         this.updateErrorList();
8191         this.getMsgEl().alignTo(this.statusBar.getEl(), this.listAlign).slideIn('b', {duration:0.3, easing:'easeOut'});
8192         this.statusBar.setText(this.hideText);
8193         this.form.getEl().on('click', this.hideErrors, this, {single:true}); // hide if the user clicks directly into the form
8194     },
8195     
8196     // private
8197     hideErrors : function(){
8198         var el = this.getMsgEl();
8199         if(el.isVisible()){
8200                 el.slideOut('b', {duration:0.2, easing:'easeIn'});
8201                 this.statusBar.setText(this.showText);
8202         }
8203         this.form.getEl().un('click', this.hideErrors, this);
8204     },
8205     
8206     // private
8207     onStatusClick : function(){
8208         if(this.getMsgEl().isVisible()){
8209             this.hideErrors();
8210         }else if(this.errors.getCount() > 0){
8211             this.showErrors();
8212         }
8213     }
8214 });