Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / examples / ux / ux-all-debug.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 Ext.ns('Ext.ux.grid');
8
9 /**
10  * @class Ext.ux.grid.BufferView
11  * @extends Ext.grid.GridView
12  * A custom GridView which renders rows on an as-needed basis.
13  */
14 Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, {
15         /**
16          * @cfg {Number} rowHeight
17          * The height of a row in the grid.
18          */
19         rowHeight: 19,
20
21         /**
22          * @cfg {Number} borderHeight
23          * The combined height of border-top and border-bottom of a row.
24          */
25         borderHeight: 2,
26
27         /**
28          * @cfg {Boolean/Number} scrollDelay
29          * The number of milliseconds before rendering rows out of the visible
30          * viewing area. Defaults to 100. Rows will render immediately with a config
31          * of false.
32          */
33         scrollDelay: 100,
34
35         /**
36          * @cfg {Number} cacheSize
37          * The number of rows to look forward and backwards from the currently viewable
38          * area.  The cache applies only to rows that have been rendered already.
39          */
40         cacheSize: 20,
41
42         /**
43          * @cfg {Number} cleanDelay
44          * The number of milliseconds to buffer cleaning of extra rows not in the
45          * cache.
46          */
47         cleanDelay: 500,
48
49         initTemplates : function(){
50                 Ext.ux.grid.BufferView.superclass.initTemplates.call(this);
51                 var ts = this.templates;
52                 // empty div to act as a place holder for a row
53                 ts.rowHolder = new Ext.Template(
54                         '<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
55                 );
56                 ts.rowHolder.disableFormats = true;
57                 ts.rowHolder.compile();
58
59                 ts.rowBody = new Ext.Template(
60                         '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
61                         '<tbody><tr>{cells}</tr>',
62                         (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
63                         '</tbody></table>'
64                 );
65                 ts.rowBody.disableFormats = true;
66                 ts.rowBody.compile();
67         },
68
69         getStyleRowHeight : function(){
70                 return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
71         },
72
73         getCalculatedRowHeight : function(){
74                 return this.rowHeight + this.borderHeight;
75         },
76
77         getVisibleRowCount : function(){
78                 var rh = this.getCalculatedRowHeight();
79                 var visibleHeight = this.scroller.dom.clientHeight;
80                 return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh);
81         },
82
83         getVisibleRows: function(){
84                 var count = this.getVisibleRowCount();
85                 var sc = this.scroller.dom.scrollTop;
86                 var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-1);
87                 return {
88                         first: Math.max(start, 0),
89                         last: Math.min(start + count + 2, this.ds.getCount()-1)
90                 };
91         },
92
93         doRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
94                 var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
95                 var rh = this.getStyleRowHeight();
96                 var vr = this.getVisibleRows();
97                 var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
98                 // buffers
99                 var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
100                 for (var j = 0, len = rs.length; j < len; j++) {
101                         r = rs[j]; cb = [];
102                         var rowIndex = (j+startRow);
103                         var visible = rowIndex >= vr.first && rowIndex <= vr.last;
104                         if (visible) {
105                                 for (var i = 0; i < colCount; i++) {
106                                         c = cs[i];
107                                         p.id = c.id;
108                                         p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
109                                         p.attr = p.cellAttr = "";
110                                         p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
111                                         p.style = c.style;
112                                         if (p.value == undefined || p.value === "") {
113                                                 p.value = "&#160;";
114                                         }
115                                         if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
116                                                 p.css += ' x-grid3-dirty-cell';
117                                         }
118                                         cb[cb.length] = ct.apply(p);
119                                 }
120                         }
121                         var alt = [];
122                         if(stripe && ((rowIndex+1) % 2 == 0)){
123                             alt[0] = "x-grid3-row-alt";
124                         }
125                         if(r.dirty){
126                             alt[1] = " x-grid3-dirty-row";
127                         }
128                         rp.cols = colCount;
129                         if(this.getRowClass){
130                             alt[2] = this.getRowClass(r, rowIndex, rp, ds);
131                         }
132                         rp.alt = alt.join(" ");
133                         rp.cells = cb.join("");
134                         buf[buf.length] =  !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
135                 }
136                 return buf.join("");
137         },
138
139         isRowRendered: function(index){
140                 var row = this.getRow(index);
141                 return row && row.childNodes.length > 0;
142         },
143
144         syncScroll: function(){
145                 Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments);
146                 this.update();
147         },
148
149         // a (optionally) buffered method to update contents of gridview
150         update: function(){
151                 if (this.scrollDelay) {
152                         if (!this.renderTask) {
153                                 this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
154                         }
155                         this.renderTask.delay(this.scrollDelay);
156                 }else{
157                         this.doUpdate();
158                 }
159         },
160
161         doUpdate: function(){
162                 if (this.getVisibleRowCount() > 0) {
163                         var g = this.grid, cm = g.colModel, ds = g.store;
164                         var cs = this.getColumnData();
165
166                         var vr = this.getVisibleRows();
167                         for (var i = vr.first; i <= vr.last; i++) {
168                                 // if row is NOT rendered and is visible, render it
169                                 if(!this.isRowRendered(i)){
170                                         var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
171                                         this.getRow(i).innerHTML = html;
172                                 }
173                         }
174                         this.clean();
175                 }
176         },
177
178         // a buffered method to clean rows
179         clean : function(){
180                 if(!this.cleanTask){
181                         this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
182                 }
183                 this.cleanTask.delay(this.cleanDelay);
184         },
185
186         doClean: function(){
187                 if (this.getVisibleRowCount() > 0) {
188                         var vr = this.getVisibleRows();
189                         vr.first -= this.cacheSize;
190                         vr.last += this.cacheSize;
191
192                         var i = 0, rows = this.getRows();
193                         // if first is less than 0, all rows have been rendered
194                         // so lets clean the end...
195                         if(vr.first <= 0){
196                                 i = vr.last + 1;
197                         }
198                         for(var len = this.ds.getCount(); i < len; i++){
199                                 // if current row is outside of first and last and
200                                 // has content, update the innerHTML to nothing
201                                 if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
202                                         rows[i].innerHTML = '';
203                                 }
204                         }
205                 }
206         },
207
208         layout: function(){
209                 Ext.ux.grid.BufferView.superclass.layout.call(this);
210                 this.update();
211         }
212 });
213 // We are adding these custom layouts to a namespace that does not
214 // exist by default in Ext, so we have to add the namespace first:
215 Ext.ns('Ext.ux.layout');
216
217 /**
218  * @class Ext.ux.layout.CenterLayout
219  * @extends Ext.layout.FitLayout
220  * <p>This is a very simple layout style used to center contents within a container.  This layout works within
221  * nested containers and can also be used as expected as a Viewport layout to center the page layout.</p>
222  * <p>As a subclass of FitLayout, CenterLayout expects to have a single child panel of the container that uses
223  * the layout.  The layout does not require any config options, although the child panel contained within the
224  * layout must provide a fixed or percentage width.  The child panel's height will fit to the container by
225  * default, but you can specify <tt>autoHeight:true</tt> to allow it to autosize based on its content height.
226  * Example usage:</p>
227  * <pre><code>
228 // The content panel is centered in the container
229 var p = new Ext.Panel({
230     title: 'Center Layout',
231     layout: 'ux.center',
232     items: [{
233         title: 'Centered Content',
234         width: '75%',
235         html: 'Some content'
236     }]
237 });
238
239 // If you leave the title blank and specify no border
240 // you'll create a non-visual, structural panel just
241 // for centering the contents in the main container.
242 var p = new Ext.Panel({
243     layout: 'ux.center',
244     border: false,
245     items: [{
246         title: 'Centered Content',
247         width: 300,
248         autoHeight: true,
249         html: 'Some content'
250     }]
251 });
252 </code></pre>
253  */
254 Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, {
255         // private
256     setItemSize : function(item, size){
257         this.container.addClass('ux-layout-center');
258         item.addClass('ux-layout-center-item');
259         if(item && size.height > 0){
260             if(item.width){
261                 size.width = item.width;
262             }
263             item.setSize(size);
264         }
265     }
266 });
267
268 Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout;
269 Ext.ns('Ext.ux.grid');\r
270 \r
271 /**\r
272  * @class Ext.ux.grid.CheckColumn\r
273  * @extends Object\r
274  * GridPanel plugin to add a column with check boxes to a grid.\r
275  * <p>Example usage:</p>\r
276  * <pre><code>\r
277 // create the column\r
278 var checkColumn = new Ext.grid.CheckColumn({\r
279    header: 'Indoor?',\r
280    dataIndex: 'indoor',\r
281    id: 'check',\r
282    width: 55\r
283 });\r
284 \r
285 // add the column to the column model\r
286 var cm = new Ext.grid.ColumnModel([{\r
287        header: 'Foo',\r
288        ...\r
289     },\r
290     checkColumn\r
291 ]);\r
292 \r
293 // create the grid\r
294 var grid = new Ext.grid.EditorGridPanel({\r
295     ...\r
296     cm: cm,\r
297     plugins: [checkColumn], // include plugin\r
298     ...\r
299 });\r
300  * </code></pre>\r
301  * In addition to storing a Boolean value within the record data, this\r
302  * class toggles a css class between <tt>'x-grid3-check-col'</tt> and\r
303  * <tt>'x-grid3-check-col-on'</tt> to alter the background image used for\r
304  * a column.\r
305  */\r
306 Ext.ux.grid.CheckColumn = function(config){\r
307     Ext.apply(this, config);\r
308     if(!this.id){\r
309         this.id = Ext.id();\r
310     }\r
311     this.renderer = this.renderer.createDelegate(this);\r
312 };\r
313 \r
314 Ext.ux.grid.CheckColumn.prototype ={\r
315     init : function(grid){\r
316         this.grid = grid;\r
317         this.grid.on('render', function(){\r
318             var view = this.grid.getView();\r
319             view.mainBody.on('mousedown', this.onMouseDown, this);\r
320         }, this);\r
321     },\r
322 \r
323     onMouseDown : function(e, t){\r
324         if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){\r
325             e.stopEvent();\r
326             var index = this.grid.getView().findRowIndex(t);\r
327             var record = this.grid.store.getAt(index);\r
328             record.set(this.dataIndex, !record.data[this.dataIndex]);\r
329         }\r
330     },\r
331 \r
332     renderer : function(v, p, record){\r
333         p.css += ' x-grid3-check-col-td'; \r
334         return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'">&#160;</div>';\r
335     }\r
336 };\r
337 \r
338 // register ptype\r
339 Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);\r
340 \r
341 // backwards compat\r
342 Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.tree');\r
343 \r
344 /**\r
345  * @class Ext.ux.tree.ColumnTree\r
346  * @extends Ext.tree.TreePanel\r
347  * \r
348  * @xtype columntree\r
349  */\r
350 Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {\r
351     lines : false,\r
352     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell\r
353     cls : 'x-column-tree',\r
354 \r
355     onRender : function(){\r
356         Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);\r
357         this.headers = this.header.createChild({cls:'x-tree-headers'});\r
358 \r
359         var cols = this.columns, c;\r
360         var totalWidth = 0;\r
361         var scrollOffset = 19; // similar to Ext.grid.GridView default\r
362 \r
363         for(var i = 0, len = cols.length; i < len; i++){\r
364              c = cols[i];\r
365              totalWidth += c.width;\r
366              this.headers.createChild({\r
367                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),\r
368                  cn: {\r
369                      cls:'x-tree-hd-text',\r
370                      html: c.header\r
371                  },\r
372                  style:'width:'+(c.width-this.borderWidth)+'px;'\r
373              });\r
374         }\r
375         this.headers.createChild({cls:'x-clear'});\r
376         // prevent floats from wrapping when clipped\r
377         this.headers.setWidth(totalWidth+scrollOffset);\r
378         this.innerCt.setWidth(totalWidth);\r
379     }\r
380 });\r
381 \r
382 Ext.reg('columntree', Ext.ux.tree.ColumnTree);\r
383 \r
384 //backwards compat\r
385 Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree;\r
386 \r
387 \r
388 /**\r
389  * @class Ext.ux.tree.ColumnNodeUI\r
390  * @extends Ext.tree.TreeNodeUI\r
391  */\r
392 Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {\r
393     focus: Ext.emptyFn, // prevent odd scrolling behavior\r
394 \r
395     renderElements : function(n, a, targetNode, bulkRender){\r
396         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';\r
397 \r
398         var t = n.getOwnerTree();\r
399         var cols = t.columns;\r
400         var bw = t.borderWidth;\r
401         var c = cols[0];\r
402 \r
403         var buf = [\r
404              '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',\r
405                 '<div class="x-tree-col" style="width:',c.width-bw,'px;">',\r
406                     '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",\r
407                     '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',\r
408                     '<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
409                     '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',\r
410                     a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',\r
411                     '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",\r
412                 "</div>"];\r
413          for(var i = 1, len = cols.length; i < len; i++){\r
414              c = cols[i];\r
415 \r
416              buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',\r
417                         '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",\r
418                       "</div>");\r
419          }\r
420          buf.push(\r
421             '<div class="x-clear"></div></div>',\r
422             '<ul class="x-tree-node-ct" style="display:none;"></ul>',\r
423             "</li>");\r
424 \r
425         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){\r
426             this.wrap = Ext.DomHelper.insertHtml("beforeBegin",\r
427                                 n.nextSibling.ui.getEl(), buf.join(""));\r
428         }else{\r
429             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));\r
430         }\r
431 \r
432         this.elNode = this.wrap.childNodes[0];\r
433         this.ctNode = this.wrap.childNodes[1];\r
434         var cs = this.elNode.firstChild.childNodes;\r
435         this.indentNode = cs[0];\r
436         this.ecNode = cs[1];\r
437         this.iconNode = cs[2];\r
438         this.anchor = cs[3];\r
439         this.textNode = cs[3].firstChild;\r
440     }\r
441 });\r
442 \r
443 //backwards compat\r
444 Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI;\r
445 /**\r
446  * @class Ext.DataView.LabelEditor\r
447  * @extends Ext.Editor\r
448  * \r
449  */\r
450 Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, {\r
451     alignment: "tl-tl",\r
452     hideEl : false,\r
453     cls: "x-small-editor",\r
454     shim: false,\r
455     completeOnEnter: true,\r
456     cancelOnEsc: true,\r
457     labelSelector: 'span.x-editable',\r
458     \r
459     constructor: function(cfg, field){\r
460         Ext.DataView.LabelEditor.superclass.constructor.call(this,\r
461             field || new Ext.form.TextField({\r
462                 allowBlank: false,\r
463                 growMin:90,\r
464                 growMax:240,\r
465                 grow:true,\r
466                 selectOnFocus:true\r
467             }), cfg\r
468         );\r
469     },\r
470     \r
471     init : function(view){\r
472         this.view = view;\r
473         view.on('render', this.initEditor, this);\r
474         this.on('complete', this.onSave, this);\r
475     },\r
476 \r
477     initEditor : function(){\r
478         this.view.on({\r
479             scope: this,\r
480             containerclick: this.doBlur,\r
481             click: this.doBlur\r
482         });\r
483         this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});\r
484     },\r
485     \r
486     doBlur: function(){\r
487         if(this.editing){\r
488             this.field.blur();\r
489         }\r
490     },\r
491 \r
492     onMouseDown : function(e, target){\r
493         if(!e.ctrlKey && !e.shiftKey){\r
494             var item = this.view.findItemFromChild(target);\r
495             e.stopEvent();\r
496             var record = this.view.store.getAt(this.view.indexOf(item));\r
497             this.startEdit(target, record.data[this.dataIndex]);\r
498             this.activeRecord = record;\r
499         }else{\r
500             e.preventDefault();\r
501         }\r
502     },\r
503 \r
504     onSave : function(ed, value){\r
505         this.activeRecord.set(this.dataIndex, value);\r
506     }\r
507 });\r
508 \r
509 \r
510 Ext.DataView.DragSelector = function(cfg){\r
511     cfg = cfg || {};\r
512     var view, proxy, tracker;\r
513     var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);\r
514     var dragSafe = cfg.dragSafe === true;\r
515 \r
516     this.init = function(dataView){\r
517         view = dataView;\r
518         view.on('render', onRender);\r
519     };\r
520 \r
521     function fillRegions(){\r
522         rs = [];\r
523         view.all.each(function(el){\r
524             rs[rs.length] = el.getRegion();\r
525         });\r
526         bodyRegion = view.el.getRegion();\r
527     }\r
528 \r
529     function cancelClick(){\r
530         return false;\r
531     }\r
532 \r
533     function onBeforeStart(e){\r
534         return !dragSafe || e.target == view.el.dom;\r
535     }\r
536 \r
537     function onStart(e){\r
538         view.on('containerclick', cancelClick, view, {single:true});\r
539         if(!proxy){\r
540             proxy = view.el.createChild({cls:'x-view-selector'});\r
541         }else{\r
542             proxy.setDisplayed('block');\r
543         }\r
544         fillRegions();\r
545         view.clearSelections();\r
546     }\r
547 \r
548     function onDrag(e){\r
549         var startXY = tracker.startXY;\r
550         var xy = tracker.getXY();\r
551 \r
552         var x = Math.min(startXY[0], xy[0]);\r
553         var y = Math.min(startXY[1], xy[1]);\r
554         var w = Math.abs(startXY[0] - xy[0]);\r
555         var h = Math.abs(startXY[1] - xy[1]);\r
556 \r
557         dragRegion.left = x;\r
558         dragRegion.top = y;\r
559         dragRegion.right = x+w;\r
560         dragRegion.bottom = y+h;\r
561 \r
562         dragRegion.constrainTo(bodyRegion);\r
563         proxy.setRegion(dragRegion);\r
564 \r
565         for(var i = 0, len = rs.length; i < len; i++){\r
566             var r = rs[i], sel = dragRegion.intersect(r);\r
567             if(sel && !r.selected){\r
568                 r.selected = true;\r
569                 view.select(i, true);\r
570             }else if(!sel && r.selected){\r
571                 r.selected = false;\r
572                 view.deselect(i);\r
573             }\r
574         }\r
575     }\r
576 \r
577     function onEnd(e){\r
578         if (!Ext.isIE) {\r
579             view.un('containerclick', cancelClick, view);    \r
580         }        \r
581         if(proxy){\r
582             proxy.setDisplayed(false);\r
583         }\r
584     }\r
585 \r
586     function onRender(view){\r
587         tracker = new Ext.dd.DragTracker({\r
588             onBeforeStart: onBeforeStart,\r
589             onStart: onStart,\r
590             onDrag: onDrag,\r
591             onEnd: onEnd\r
592         });\r
593         tracker.initEl(view.el);\r
594     }\r
595 };Ext.ns('Ext.ux.form');
596
597 /**
598  * @class Ext.ux.form.FileUploadField
599  * @extends Ext.form.TextField
600  * Creates a file upload field.
601  * @xtype fileuploadfield
602  */
603 Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
604     /**
605      * @cfg {String} buttonText The button text to display on the upload button (defaults to
606      * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
607      * value will be used instead if available.
608      */
609     buttonText: 'Browse...',
610     /**
611      * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
612      * text field (defaults to false).  If true, all inherited TextField members will still be available.
613      */
614     buttonOnly: false,
615     /**
616      * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
617      * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
618      */
619     buttonOffset: 3,
620     /**
621      * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
622      */
623
624     // private
625     readOnly: true,
626
627     /**
628      * @hide
629      * @method autoSize
630      */
631     autoSize: Ext.emptyFn,
632
633     // private
634     initComponent: function(){
635         Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
636
637         this.addEvents(
638             /**
639              * @event fileselected
640              * Fires when the underlying file input field's value has changed from the user
641              * selecting a new file from the system file selection dialog.
642              * @param {Ext.ux.form.FileUploadField} this
643              * @param {String} value The file value returned by the underlying file input field
644              */
645             'fileselected'
646         );
647     },
648
649     // private
650     onRender : function(ct, position){
651         Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);
652
653         this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
654         this.el.addClass('x-form-file-text');
655         this.el.dom.removeAttribute('name');
656
657         this.fileInput = this.wrap.createChild({
658             id: this.getFileInputId(),
659             name: this.name||this.getId(),
660             cls: 'x-form-file',
661             tag: 'input',
662             type: 'file',
663             size: 1
664         });
665
666         var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
667             text: this.buttonText
668         });
669         this.button = new Ext.Button(Ext.apply(btnCfg, {
670             renderTo: this.wrap,
671             cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
672         }));
673
674         if(this.buttonOnly){
675             this.el.hide();
676             this.wrap.setWidth(this.button.getEl().getWidth());
677         }
678
679         this.fileInput.on('change', function(){
680             var v = this.fileInput.dom.value;
681             this.setValue(v);
682             this.fireEvent('fileselected', this, v);
683         }, this);
684     },
685
686     // private
687     getFileInputId: function(){
688         return this.id + '-file';
689     },
690
691     // private
692     onResize : function(w, h){
693         Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
694
695         this.wrap.setWidth(w);
696
697         if(!this.buttonOnly){
698             var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
699             this.el.setWidth(w);
700         }
701     },
702
703     // private
704     onDestroy: function(){
705         Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
706         Ext.destroy(this.fileInput, this.button, this.wrap);
707     },
708
709
710     // private
711     preFocus : Ext.emptyFn,
712
713     // private
714     getResizeEl : function(){
715         return this.wrap;
716     },
717
718     // private
719     getPositionEl : function(){
720         return this.wrap;
721     },
722
723     // private
724     alignErrorIcon : function(){
725         this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
726     }
727
728 });
729
730 Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
731
732 // backwards compat
733 Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
734 (function(){\r
735 Ext.ns('Ext.a11y');\r
736 \r
737 Ext.a11y.Frame = Ext.extend(Object, {\r
738     initialized: false,\r
739     \r
740     constructor: function(size, color){\r
741         this.setSize(size || 1);\r
742         this.setColor(color || '15428B');\r
743     },\r
744     \r
745     init: function(){\r
746         if (!this.initialized) {\r
747             this.sides = [];\r
748             \r
749             var s, i;\r
750             \r
751             this.ct = Ext.DomHelper.append(document.body, {\r
752                 cls: 'x-a11y-focusframe'\r
753             }, true);\r
754             \r
755             for (i = 0; i < 4; i++) {\r
756                 s = Ext.DomHelper.append(this.ct, {\r
757                     cls: 'x-a11y-focusframe-side',\r
758                     style: 'background-color: #' + this.color\r
759                 }, true);\r
760                 s.visibilityMode = Ext.Element.DISPLAY;\r
761                 this.sides.push(s);\r
762             }\r
763             \r
764             this.frameTask = new Ext.util.DelayedTask(function(el){\r
765                 var newEl = Ext.get(el);\r
766                 if (newEl != this.curEl) {\r
767                     var w = newEl.getWidth();\r
768                     var h = newEl.getHeight();\r
769                     this.sides[0].show().setSize(w, this.size).anchorTo(el, 'tl', [0, -1]);\r
770                     this.sides[2].show().setSize(w, this.size).anchorTo(el, 'bl', [0, -1]);\r
771                     this.sides[1].show().setSize(this.size, h).anchorTo(el, 'tr', [-1, 0]);\r
772                     this.sides[3].show().setSize(this.size, h).anchorTo(el, 'tl', [-1, 0]);\r
773                     this.curEl = newEl;\r
774                 }\r
775             }, this);\r
776             \r
777             this.unframeTask = new Ext.util.DelayedTask(function(){\r
778                 if (this.initialized) {\r
779                     this.sides[0].hide();\r
780                     this.sides[1].hide();\r
781                     this.sides[2].hide();\r
782                     this.sides[3].hide();\r
783                     this.curEl = null;\r
784                 }\r
785             }, this);\r
786             this.initialized = true;\r
787         }\r
788     },\r
789     \r
790     frame: function(el){\r
791         this.init();\r
792         this.unframeTask.cancel();\r
793         this.frameTask.delay(2, false, false, [el]);\r
794     },\r
795     \r
796     unframe: function(){\r
797         this.init();\r
798         this.unframeTask.delay(2);\r
799     },\r
800     \r
801     setSize: function(size){\r
802         this.size = size;\r
803     },\r
804     \r
805     setColor: function(color){\r
806         this.color = color;\r
807     }\r
808 });\r
809 \r
810 Ext.a11y.FocusFrame = new Ext.a11y.Frame(2, '15428B');\r
811 Ext.a11y.RelayFrame = new Ext.a11y.Frame(1, '6B8CBF');\r
812 \r
813 Ext.a11y.Focusable = Ext.extend(Ext.util.Observable, {\r
814     constructor: function(el, relayTo, noFrame, frameEl){\r
815         Ext.a11y.Focusable.superclass.constructor.call(this);\r
816         \r
817         this.addEvents('focus', 'blur', 'left', 'right', 'up', 'down', 'esc', 'enter', 'space');\r
818         \r
819         if (el instanceof Ext.Component) {\r
820             this.el = el.el;\r
821             this.setComponent(el);\r
822         }\r
823         else {\r
824             this.el = Ext.get(el);\r
825             this.setComponent(null);\r
826         }\r
827         \r
828         this.setRelayTo(relayTo)\r
829         this.setNoFrame(noFrame);\r
830         this.setFrameEl(frameEl);\r
831         \r
832         this.init();\r
833         \r
834         Ext.a11y.FocusMgr.register(this);\r
835     },\r
836     \r
837     init: function(){\r
838         this.el.dom.tabIndex = '1';\r
839         this.el.addClass('x-a11y-focusable');\r
840         this.el.on({\r
841             focus: this.onFocus,\r
842             blur: this.onBlur,\r
843             keydown: this.onKeyDown,\r
844             scope: this\r
845         });\r
846     },\r
847     \r
848     setRelayTo: function(relayTo){\r
849         this.relayTo = relayTo ? Ext.a11y.FocusMgr.get(relayTo) : null;\r
850     },\r
851     \r
852     setNoFrame: function(noFrame){\r
853         this.noFrame = (noFrame === true) ? true : false;\r
854     },\r
855     \r
856     setFrameEl: function(frameEl){\r
857         this.frameEl = frameEl && Ext.get(frameEl) || this.el;\r
858     },\r
859     \r
860     setComponent: function(cmp){\r
861         this.component = cmp || null;\r
862     },\r
863     \r
864     onKeyDown: function(e, t){\r
865         var k = e.getKey(), SK = Ext.a11y.Focusable.SpecialKeys, ret, tf;\r
866         \r
867         tf = (t !== this.el.dom) ? Ext.a11y.FocusMgr.get(t, true) : this;\r
868         if (!tf) {\r
869             // this can happen when you are on a focused item within a panel body\r
870             // that is not a Ext.a11y.Focusable\r
871             tf = Ext.a11y.FocusMgr.get(Ext.fly(t).parent('.x-a11y-focusable'));\r
872         }\r
873         \r
874         if (SK[k] !== undefined) {\r
875             ret = this.fireEvent(SK[k], e, t, tf, this);\r
876         }\r
877         if (ret === false || this.fireEvent('keydown', e, t, tf, this) === false) {\r
878             e.stopEvent();\r
879         }\r
880     },\r
881     \r
882     focus: function(){\r
883         this.el.dom.focus();\r
884     },\r
885     \r
886     blur: function(){\r
887         this.el.dom.blur();\r
888     },\r
889     \r
890     onFocus: function(e, t){\r
891         this.el.addClass('x-a11y-focused');\r
892         if (this.relayTo) {\r
893             this.relayTo.el.addClass('x-a11y-focused-relay');\r
894             if (!this.relayTo.noFrame) {\r
895                 Ext.a11y.FocusFrame.frame(this.relayTo.frameEl);\r
896             }\r
897             if (!this.noFrame) {\r
898                 Ext.a11y.RelayFrame.frame(this.frameEl);\r
899             }\r
900         }\r
901         else {\r
902             if (!this.noFrame) {\r
903                 Ext.a11y.FocusFrame.frame(this.frameEl);\r
904             }\r
905         }\r
906         \r
907         this.fireEvent('focus', e, t, this);\r
908     },\r
909     \r
910     onBlur: function(e, t){\r
911         if (this.relayTo) {\r
912             this.relayTo.el.removeClass('x-a11y-focused-relay');\r
913             Ext.a11y.RelayFrame.unframe();\r
914         }\r
915         this.el.removeClass('x-a11y-focused');\r
916         Ext.a11y.FocusFrame.unframe();\r
917         this.fireEvent('blur', e, t, this);\r
918     },\r
919     \r
920     destroy: function(){\r
921         this.el.un('keydown', this.onKeyDown);\r
922         this.el.un('focus', this.onFocus);\r
923         this.el.un('blur', this.onBlur);\r
924         this.el.removeClass('x-a11y-focusable');\r
925         this.el.removeClass('x-a11y-focused');\r
926         if (this.relayTo) {\r
927             this.relayTo.el.removeClass('x-a11y-focused-relay');\r
928         }\r
929     }\r
930 });\r
931 \r
932 Ext.a11y.FocusItem = Ext.extend(Object, {\r
933     constructor: function(el, enableTabbing){\r
934         Ext.a11y.FocusItem.superclass.constructor.call(this);\r
935         \r
936         this.el = Ext.get(el);\r
937         this.fi = new Ext.a11y.Focusable(el);\r
938         this.fi.setComponent(this);\r
939         \r
940         this.fi.on('tab', this.onTab, this);\r
941         \r
942         this.enableTabbing = enableTabbing === true ? true : false;\r
943     },\r
944     \r
945     getEnterItem: function(){\r
946         if (this.enableTabbing) {\r
947             var items = this.getFocusItems();\r
948             if (items && items.length) {\r
949                 return items[0];\r
950             }\r
951         }\r
952     },\r
953     \r
954     getFocusItems: function(){\r
955         if (this.enableTabbing) {\r
956             return this.el.query('a, button, input, select');\r
957         }\r
958         return null;\r
959     },\r
960     \r
961     onTab: function(e, t){\r
962         var items = this.getFocusItems(), i;\r
963         \r
964         if (items && items.length && (i = items.indexOf(t)) !== -1) {\r
965             if (e.shiftKey && i > 0) {\r
966                 e.stopEvent();\r
967                 items[i - 1].focus();\r
968                 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
969                 return;\r
970             }\r
971             else \r
972                 if (!e.shiftKey && i < items.length - 1) {\r
973                     e.stopEvent();\r
974                     items[i + 1].focus();\r
975                     Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
976                     return;\r
977                 }\r
978         }\r
979     },\r
980     \r
981     focus: function(){\r
982         if (this.enableTabbing) {\r
983             var items = this.getFocusItems();\r
984             if (items && items.length) {\r
985                 items[0].focus();\r
986                 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
987                 return;\r
988             }\r
989         }\r
990         this.fi.focus();\r
991     },\r
992     \r
993     blur: function(){\r
994         this.fi.blur();\r
995     }\r
996 });\r
997 \r
998 Ext.a11y.FocusMgr = function(){\r
999     var all = new Ext.util.MixedCollection();\r
1000     \r
1001     return {\r
1002         register: function(f){\r
1003             all.add(f.el && Ext.id(f.el), f);\r
1004         },\r
1005         \r
1006         unregister: function(f){\r
1007             all.remove(f);\r
1008         },\r
1009         \r
1010         get: function(el, noCreate){\r
1011             return all.get(Ext.id(el)) || (noCreate ? false : new Ext.a11y.Focusable(el));\r
1012         },\r
1013         \r
1014         all: all\r
1015     }\r
1016 }();\r
1017 \r
1018 Ext.a11y.Focusable.SpecialKeys = {};\r
1019 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.LEFT] = 'left';\r
1020 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.RIGHT] = 'right';\r
1021 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.DOWN] = 'down';\r
1022 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.UP] = 'up';\r
1023 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ESC] = 'esc';\r
1024 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ENTER] = 'enter';\r
1025 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.SPACE] = 'space';\r
1026 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.TAB] = 'tab';\r
1027 \r
1028 // we use the new observeClass method to fire our new initFocus method on components\r
1029 Ext.util.Observable.observeClass(Ext.Component);\r
1030 Ext.Component.on('render', function(cmp){\r
1031     cmp.initFocus();\r
1032     cmp.initARIA();\r
1033 });\r
1034 Ext.override(Ext.Component, {\r
1035     initFocus: Ext.emptyFn,\r
1036     initARIA: Ext.emptyFn\r
1037 });\r
1038 \r
1039 Ext.override(Ext.Container, {\r
1040     isFocusable: true,\r
1041     noFocus: false,\r
1042     \r
1043     // private\r
1044     initFocus: function(){\r
1045         if (!this.fi && !this.noFocus) {\r
1046             this.fi = new Ext.a11y.Focusable(this);\r
1047         }\r
1048         this.mon(this.fi, {\r
1049             focus: this.onFocus,\r
1050             blur: this.onBlur,\r
1051             tab: this.onTab,\r
1052             enter: this.onEnter,\r
1053             esc: this.onEsc,\r
1054             scope: this\r
1055         });\r
1056         \r
1057         if (this.hidden) {\r
1058             this.isFocusable = false;\r
1059         }\r
1060         \r
1061         this.on('show', function(){\r
1062             this.isFocusable = true;\r
1063         }, this);\r
1064         this.on('hide', function(){\r
1065             this.isFocusable = false;\r
1066         }, this);\r
1067     },\r
1068     \r
1069     focus: function(){\r
1070         this.fi.focus();\r
1071     },\r
1072     \r
1073     blur: function(){\r
1074         this.fi.blur();\r
1075     },\r
1076     \r
1077     enter: function(){\r
1078         var eitem = this.getEnterItem();\r
1079         if (eitem) {\r
1080             eitem.focus();\r
1081         }\r
1082     },\r
1083     \r
1084     onFocus: Ext.emptyFn,\r
1085     onBlur: Ext.emptyFn,\r
1086     \r
1087     onTab: function(e, t, tf){\r
1088         var rf = tf.relayTo || tf;\r
1089         if (rf.component && rf.component !== this) {\r
1090             e.stopEvent();\r
1091             var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);\r
1092             item.focus();\r
1093         }\r
1094     },\r
1095     \r
1096     onEnter: function(e, t, tf){\r
1097         // check to see if enter is pressed while "on" the panel\r
1098         if (tf.component && tf.component === this) {\r
1099             e.stopEvent();\r
1100             this.enter();\r
1101         }\r
1102         e.stopPropagation();\r
1103     },\r
1104     \r
1105     onEsc: function(e, t){\r
1106         e.preventDefault();\r
1107         \r
1108         // check to see if esc is pressed while "inside" the panel\r
1109         // or while "on" the panel\r
1110         if (t === this.el.dom) {\r
1111             // "on" the panel, check if this panel has an owner panel and focus that\r
1112             // we dont stop the event in this case so that this same check will be\r
1113             // done for this ownerCt\r
1114             if (this.ownerCt) {\r
1115                 this.ownerCt.focus();\r
1116             }\r
1117         }\r
1118         else {\r
1119             // we were inside the panel when esc was pressed,\r
1120             // so go back "on" the panel\r
1121             if (this.ownerCt && this.ownerCt.isFocusable) {\r
1122                 var si = this.ownerCt.getFocusItems();\r
1123                 \r
1124                 if (si && si.getCount() > 1) {\r
1125                     e.stopEvent();\r
1126                 }\r
1127             }\r
1128             this.focus();\r
1129         }\r
1130     },\r
1131     \r
1132     getFocusItems: function(){\r
1133         return this.items &&\r
1134         this.items.filterBy(function(o){\r
1135             return o.isFocusable;\r
1136         }) ||\r
1137         null;\r
1138     },\r
1139     \r
1140     getEnterItem: function(){\r
1141         var ci = this.getFocusItems(), length = ci ? ci.getCount() : 0;\r
1142         \r
1143         if (length === 1) {\r
1144             return ci.first().getEnterItem && ci.first().getEnterItem() || ci.first();\r
1145         }\r
1146         else \r
1147             if (length > 1) {\r
1148                 return ci.first();\r
1149             }\r
1150     },\r
1151     \r
1152     getNextFocus: function(current){\r
1153         var items = this.getFocusItems(), next = current, i = items.indexOf(current), length = items.getCount();\r
1154         \r
1155         if (i === length - 1) {\r
1156             next = items.first();\r
1157         }\r
1158         else {\r
1159             next = items.get(i + 1);\r
1160         }\r
1161         return next;\r
1162     },\r
1163     \r
1164     getPreviousFocus: function(current){\r
1165         var items = this.getFocusItems(), prev = current, i = items.indexOf(current), length = items.getCount();\r
1166         \r
1167         if (i === 0) {\r
1168             prev = items.last();\r
1169         }\r
1170         else {\r
1171             prev = items.get(i - 1);\r
1172         }\r
1173         return prev;\r
1174     },\r
1175     \r
1176     getFocusable : function() {\r
1177         return this.fi;\r
1178     }\r
1179 });\r
1180 \r
1181 Ext.override(Ext.Panel, {\r
1182     /**\r
1183      * @cfg {Boolean} enableTabbing <tt>true</tt> to enable tabbing. Default is <tt>false</tt>.\r
1184      */        \r
1185     getFocusItems: function(){\r
1186         // items gets all the items inside the body\r
1187         var items = Ext.Panel.superclass.getFocusItems.call(this), bodyFocus = null;\r
1188         \r
1189         if (!items) {\r
1190             items = new Ext.util.MixedCollection();\r
1191             this.bodyFocus = this.bodyFocus || new Ext.a11y.FocusItem(this.body, this.enableTabbing);\r
1192             items.add('body', this.bodyFocus);\r
1193         }\r
1194         // but panels can also have tbar, bbar, fbar\r
1195         if (this.tbar && this.topToolbar) {\r
1196             items.insert(0, this.topToolbar);\r
1197         }\r
1198         if (this.bbar && this.bottomToolbar) {\r
1199             items.add(this.bottomToolbar);\r
1200         }\r
1201         if (this.fbar) {\r
1202             items.add(this.fbar);\r
1203         }\r
1204         \r
1205         return items;\r
1206     }\r
1207 });\r
1208 \r
1209 Ext.override(Ext.TabPanel, {\r
1210     // private\r
1211     initFocus: function(){\r
1212         Ext.TabPanel.superclass.initFocus.call(this);\r
1213         this.mon(this.fi, {\r
1214             left: this.onLeft,\r
1215             right: this.onRight,\r
1216             scope: this\r
1217         });\r
1218     },\r
1219     \r
1220     onLeft: function(e){\r
1221         if (!this.activeTab) {\r
1222             return;\r
1223         }\r
1224         e.stopEvent();\r
1225         var prev = this.items.itemAt(this.items.indexOf(this.activeTab) - 1);\r
1226         if (prev) {\r
1227             this.setActiveTab(prev);\r
1228         }\r
1229         return false;\r
1230     },\r
1231     \r
1232     onRight: function(e){\r
1233         if (!this.activeTab) {\r
1234             return;\r
1235         }\r
1236         e.stopEvent();\r
1237         var next = this.items.itemAt(this.items.indexOf(this.activeTab) + 1);\r
1238         if (next) {\r
1239             this.setActiveTab(next);\r
1240         }\r
1241         return false;\r
1242     }\r
1243 });\r
1244 \r
1245 Ext.override(Ext.tree.TreeNodeUI, {\r
1246     // private\r
1247     focus: function(){\r
1248         this.node.getOwnerTree().bodyFocus.focus();\r
1249     }\r
1250 });\r
1251 \r
1252 Ext.override(Ext.tree.TreePanel, {\r
1253     // private\r
1254     afterRender : function(){\r
1255         Ext.tree.TreePanel.superclass.afterRender.call(this);\r
1256         this.root.render();\r
1257         if(!this.rootVisible){\r
1258             this.root.renderChildren();\r
1259         }\r
1260         this.bodyFocus = new Ext.a11y.FocusItem(this.body.down('.x-tree-root-ct'));\r
1261         this.bodyFocus.fi.setFrameEl(this.body);\r
1262     } \r
1263 });\r
1264 \r
1265 Ext.override(Ext.grid.GridPanel, {\r
1266     initFocus: function(){\r
1267         Ext.grid.GridPanel.superclass.initFocus.call(this);\r
1268         this.bodyFocus = new Ext.a11y.FocusItem(this.view.focusEl);\r
1269         this.bodyFocus.fi.setFrameEl(this.body);\r
1270     }\r
1271 });\r
1272 \r
1273 Ext.override(Ext.Button, {\r
1274     isFocusable: true,\r
1275     noFocus: false,\r
1276     \r
1277     initFocus: function(){\r
1278         Ext.Button.superclass.initFocus.call(this);\r
1279         this.fi = this.fi || new Ext.a11y.Focusable(this.btnEl, null, null, this.el);\r
1280         this.fi.setComponent(this);\r
1281         \r
1282         this.mon(this.fi, {\r
1283             focus: this.onFocus,\r
1284             blur: this.onBlur,\r
1285             scope: this\r
1286         });\r
1287         \r
1288         if (this.menu) {\r
1289             this.mon(this.fi, 'down', this.showMenu, this);\r
1290             this.on('menuhide', this.focus, this);\r
1291         }\r
1292         \r
1293         if (this.hidden) {\r
1294             this.isFocusable = false;\r
1295         }\r
1296         \r
1297         this.on('show', function(){\r
1298             this.isFocusable = true;\r
1299         }, this);\r
1300         this.on('hide', function(){\r
1301             this.isFocusable = false;\r
1302         }, this);\r
1303     },\r
1304     \r
1305     focus: function(){\r
1306         this.fi.focus();\r
1307     },\r
1308     \r
1309     blur: function(){\r
1310         this.fi.blur();\r
1311     },\r
1312     \r
1313     onFocus: function(){\r
1314         if (!this.disabled) {\r
1315             this.el.addClass("x-btn-focus");\r
1316         }\r
1317     },\r
1318     \r
1319     onBlur: function(){\r
1320         this.el.removeClass("x-btn-focus");\r
1321     }\r
1322 });\r
1323 \r
1324 Ext.override(Ext.Toolbar, {\r
1325     initFocus: function(){\r
1326         Ext.Toolbar.superclass.initFocus.call(this);\r
1327         this.mon(this.fi, {\r
1328             left: this.onLeft,\r
1329             right: this.onRight,\r
1330             scope: this\r
1331         });\r
1332         \r
1333         this.on('focus', this.onButtonFocus, this, {\r
1334             stopEvent: true\r
1335         });\r
1336     },\r
1337     \r
1338     addItem: function(item){\r
1339         Ext.Toolbar.superclass.add.apply(this, arguments);\r
1340         if (item.rendered && item.fi !== undefined) {\r
1341             item.fi.setRelayTo(this.el);\r
1342             this.relayEvents(item.fi, ['focus']);\r
1343         }\r
1344         else {\r
1345             item.on('render', function(){\r
1346                 if (item.fi !== undefined) {\r
1347                     item.fi.setRelayTo(this.el);\r
1348                     this.relayEvents(item.fi, ['focus']);\r
1349                 }\r
1350             }, this, {\r
1351                 single: true\r
1352             });\r
1353         }\r
1354         return item;\r
1355     },\r
1356     \r
1357     onFocus: function(){\r
1358         var items = this.getFocusItems();\r
1359         if (items && items.getCount() > 0) {\r
1360             if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {\r
1361                 this.lastFocus.focus();\r
1362             }\r
1363             else {\r
1364                 items.first().focus();\r
1365             }\r
1366         }\r
1367     },\r
1368     \r
1369     onButtonFocus: function(e, t, tf){\r
1370         this.lastFocus = tf.component || null;\r
1371     },\r
1372     \r
1373     onLeft: function(e, t, tf){\r
1374         e.stopEvent();\r
1375         this.getPreviousFocus(tf.component).focus();\r
1376     },\r
1377     \r
1378     onRight: function(e, t, tf){\r
1379         e.stopEvent();\r
1380         this.getNextFocus(tf.component).focus();\r
1381     },\r
1382     \r
1383     getEnterItem: Ext.emptyFn,\r
1384     onTab: Ext.emptyFn,\r
1385     onEsc: Ext.emptyFn\r
1386 });\r
1387 \r
1388 Ext.override(Ext.menu.BaseItem, {\r
1389     initFocus: function(){\r
1390         this.fi = new Ext.a11y.Focusable(this, this.parentMenu && this.parentMenu.el || null, true);\r
1391     }\r
1392 });\r
1393 \r
1394 Ext.override(Ext.menu.Menu, {\r
1395     initFocus: function(){\r
1396         this.fi = new Ext.a11y.Focusable(this);\r
1397         this.focusEl = this.fi;\r
1398     }\r
1399 });\r
1400 \r
1401 Ext.a11y.WindowMgr = new Ext.WindowGroup();\r
1402 \r
1403 Ext.apply(Ext.WindowMgr, {\r
1404     bringToFront: function(win){\r
1405         Ext.a11y.WindowMgr.bringToFront.call(this, win);\r
1406         if (win.modal) {\r
1407             win.enter();\r
1408         }\r
1409         else {\r
1410             win.focus();\r
1411         }\r
1412     }\r
1413 });\r
1414 \r
1415 Ext.override(Ext.Window, {\r
1416     initFocus: function(){\r
1417         Ext.Window.superclass.initFocus.call(this);\r
1418         this.on('beforehide', function(){\r
1419             Ext.a11y.RelayFrame.unframe();\r
1420             Ext.a11y.FocusFrame.unframe();\r
1421         });\r
1422     }\r
1423 });\r
1424 \r
1425 Ext.override(Ext.form.Field, {\r
1426     isFocusable: true,\r
1427     noFocus: false,\r
1428     \r
1429     initFocus: function(){\r
1430         this.fi = this.fi || new Ext.a11y.Focusable(this, null, true);\r
1431         \r
1432         Ext.form.Field.superclass.initFocus.call(this);\r
1433         \r
1434         if (this.hidden) {\r
1435             this.isFocusable = false;\r
1436         }\r
1437         \r
1438         this.on('show', function(){\r
1439             this.isFocusable = true;\r
1440         }, this);\r
1441         this.on('hide', function(){\r
1442             this.isFocusable = false;\r
1443         }, this);\r
1444     }\r
1445 });\r
1446 \r
1447 Ext.override(Ext.FormPanel, {\r
1448     initFocus: function(){\r
1449         Ext.FormPanel.superclass.initFocus.call(this);\r
1450         this.on('focus', this.onFieldFocus, this, {\r
1451             stopEvent: true\r
1452         });\r
1453     },\r
1454     \r
1455     // private\r
1456     createForm: function(){\r
1457         delete this.initialConfig.listeners;\r
1458         var form = new Ext.form.BasicForm(null, this.initialConfig);\r
1459         form.afterMethod('add', this.formItemAdd, this);\r
1460         return form;\r
1461     },\r
1462     \r
1463     formItemAdd: function(item){\r
1464         item.on('render', function(field){\r
1465             field.fi.setRelayTo(this.el);\r
1466             this.relayEvents(field.fi, ['focus']);\r
1467         }, this, {\r
1468             single: true\r
1469         });\r
1470     },\r
1471     \r
1472     onFocus: function(){\r
1473         var items = this.getFocusItems();\r
1474         if (items && items.getCount() > 0) {\r
1475             if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {\r
1476                 this.lastFocus.focus();\r
1477             }\r
1478             else {\r
1479                 items.first().focus();\r
1480             }\r
1481         }\r
1482     },\r
1483     \r
1484     onFieldFocus: function(e, t, tf){\r
1485         this.lastFocus = tf.component || null;\r
1486     },\r
1487     \r
1488     onTab: function(e, t, tf){\r
1489         if (tf.relayTo.component === this) {\r
1490             var item = e.shiftKey ? this.getPreviousFocus(tf.component) : this.getNextFocus(tf.component);\r
1491             \r
1492             if (item) {\r
1493                 ev.stopEvent();\r
1494                 item.focus();\r
1495                 return;\r
1496             }\r
1497         }\r
1498         Ext.FormPanel.superclass.onTab.apply(this, arguments);\r
1499     },\r
1500     \r
1501     getNextFocus: function(current){\r
1502         var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();\r
1503         \r
1504         return (i < length - 1) ? items.get(i + 1) : false;\r
1505     },\r
1506     \r
1507     getPreviousFocus: function(current){\r
1508         var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();\r
1509         \r
1510         return (i > 0) ? items.get(i - 1) : false;\r
1511     }\r
1512 });\r
1513 \r
1514 Ext.override(Ext.Viewport, {\r
1515     initFocus: function(){\r
1516         Ext.Viewport.superclass.initFocus.apply(this);\r
1517         this.mon(Ext.get(document), 'focus', this.focus, this);\r
1518         this.mon(Ext.get(document), 'blur', this.blur, this);\r
1519         this.fi.setNoFrame(true);\r
1520     },\r
1521     \r
1522     onTab: function(e, t, tf, f){\r
1523         e.stopEvent();\r
1524         \r
1525         if (tf === f) {\r
1526             items = this.getFocusItems();\r
1527             if (items && items.getCount() > 0) {\r
1528                 items.first().focus();\r
1529             }\r
1530         }\r
1531         else {\r
1532             var rf = tf.relayTo || tf;\r
1533             var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);\r
1534             item.focus();\r
1535         }\r
1536     }\r
1537 });\r
1538     \r
1539 })();/**\r
1540  * @class Ext.ux.GMapPanel\r
1541  * @extends Ext.Panel\r
1542  * @author Shea Frederick\r
1543  */\r
1544 Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {\r
1545     initComponent : function(){\r
1546         \r
1547         var defConfig = {\r
1548             plain: true,\r
1549             zoomLevel: 3,\r
1550             yaw: 180,\r
1551             pitch: 0,\r
1552             zoom: 0,\r
1553             gmapType: 'map',\r
1554             border: false\r
1555         };\r
1556         \r
1557         Ext.applyIf(this,defConfig);\r
1558         \r
1559         Ext.ux.GMapPanel.superclass.initComponent.call(this);        \r
1560 \r
1561     },\r
1562     afterRender : function(){\r
1563         \r
1564         var wh = this.ownerCt.getSize();\r
1565         Ext.applyIf(this, wh);\r
1566         \r
1567         Ext.ux.GMapPanel.superclass.afterRender.call(this);    \r
1568         \r
1569         if (this.gmapType === 'map'){\r
1570             this.gmap = new GMap2(this.body.dom);\r
1571         }\r
1572         \r
1573         if (this.gmapType === 'panorama'){\r
1574             this.gmap = new GStreetviewPanorama(this.body.dom);\r
1575         }\r
1576         \r
1577         if (typeof this.addControl == 'object' && this.gmapType === 'map') {\r
1578             this.gmap.addControl(this.addControl);\r
1579         }\r
1580         \r
1581         if (typeof this.setCenter === 'object') {\r
1582             if (typeof this.setCenter.geoCodeAddr === 'string'){\r
1583                 this.geoCodeLookup(this.setCenter.geoCodeAddr);\r
1584             }else{\r
1585                 if (this.gmapType === 'map'){\r
1586                     var point = new GLatLng(this.setCenter.lat,this.setCenter.lng);\r
1587                     this.gmap.setCenter(point, this.zoomLevel);    \r
1588                 }\r
1589                 if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
1590                     this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);\r
1591                 }\r
1592             }\r
1593             if (this.gmapType === 'panorama'){\r
1594                 this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});\r
1595             }\r
1596         }\r
1597 \r
1598         GEvent.bind(this.gmap, 'load', this, function(){\r
1599             this.onMapReady();\r
1600         });\r
1601 \r
1602     },\r
1603     onMapReady : function(){\r
1604         this.addMarkers(this.markers);\r
1605         this.addMapControls();\r
1606         this.addOptions();  \r
1607     },\r
1608     onResize : function(w, h){\r
1609 \r
1610         if (typeof this.getMap() == 'object') {\r
1611             this.gmap.checkResize();\r
1612         }\r
1613         \r
1614         Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);\r
1615 \r
1616     },\r
1617     setSize : function(width, height, animate){\r
1618         \r
1619         if (typeof this.getMap() == 'object') {\r
1620             this.gmap.checkResize();\r
1621         }\r
1622         \r
1623         Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);\r
1624         \r
1625     },\r
1626     getMap : function(){\r
1627         \r
1628         return this.gmap;\r
1629         \r
1630     },\r
1631     getCenter : function(){\r
1632         \r
1633         return this.getMap().getCenter();\r
1634         \r
1635     },\r
1636     getCenterLatLng : function(){\r
1637         \r
1638         var ll = this.getCenter();\r
1639         return {lat: ll.lat(), lng: ll.lng()};\r
1640         \r
1641     },\r
1642     addMarkers : function(markers) {\r
1643         \r
1644         if (Ext.isArray(markers)){\r
1645             for (var i = 0; i < markers.length; i++) {\r
1646                 var mkr_point = new GLatLng(markers[i].lat,markers[i].lng);\r
1647                 this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners);\r
1648             }\r
1649         }\r
1650         \r
1651     },\r
1652     addMarker : function(point, marker, clear, center, listeners){\r
1653         \r
1654         Ext.applyIf(marker,G_DEFAULT_ICON);\r
1655 \r
1656         if (clear === true){\r
1657             this.getMap().clearOverlays();\r
1658         }\r
1659         if (center === true) {\r
1660             this.getMap().setCenter(point, this.zoomLevel);\r
1661         }\r
1662 \r
1663         var mark = new GMarker(point,marker);\r
1664         if (typeof listeners === 'object'){\r
1665             for (evt in listeners) {\r
1666                 GEvent.bind(mark, evt, this, listeners[evt]);\r
1667             }\r
1668         }\r
1669         this.getMap().addOverlay(mark);\r
1670 \r
1671     },\r
1672     addMapControls : function(){\r
1673         \r
1674         if (this.gmapType === 'map') {\r
1675             if (Ext.isArray(this.mapControls)) {\r
1676                 for(i=0;i<this.mapControls.length;i++){\r
1677                     this.addMapControl(this.mapControls[i]);\r
1678                 }\r
1679             }else if(typeof this.mapControls === 'string'){\r
1680                 this.addMapControl(this.mapControls);\r
1681             }else if(typeof this.mapControls === 'object'){\r
1682                 this.getMap().addControl(this.mapControls);\r
1683             }\r
1684         }\r
1685         \r
1686     },\r
1687     addMapControl : function(mc){\r
1688         \r
1689         var mcf = window[mc];\r
1690         if (typeof mcf === 'function') {\r
1691             this.getMap().addControl(new mcf());\r
1692         }    \r
1693         \r
1694     },\r
1695     addOptions : function(){\r
1696         \r
1697         if (Ext.isArray(this.mapConfOpts)) {\r
1698             var mc;\r
1699             for(i=0;i<this.mapConfOpts.length;i++){\r
1700                 this.addOption(this.mapConfOpts[i]);\r
1701             }\r
1702         }else if(typeof this.mapConfOpts === 'string'){\r
1703             this.addOption(this.mapConfOpts);\r
1704         }        \r
1705         \r
1706     },\r
1707     addOption : function(mc){\r
1708         \r
1709         var mcf = this.getMap()[mc];\r
1710         if (typeof mcf === 'function') {\r
1711             this.getMap()[mc]();\r
1712         }    \r
1713         \r
1714     },\r
1715     geoCodeLookup : function(addr) {\r
1716         \r
1717         this.geocoder = new GClientGeocoder();\r
1718         this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));\r
1719         \r
1720     },\r
1721     addAddressToMap : function(response) {\r
1722         \r
1723         if (!response || response.Status.code != 200) {\r
1724             Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');\r
1725         }else{\r
1726             place = response.Placemark[0];\r
1727             addressinfo = place.AddressDetails;\r
1728             accuracy = addressinfo.Accuracy;\r
1729             if (accuracy === 0) {\r
1730                 Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');\r
1731             }else{\r
1732                 if (accuracy < 7) {\r
1733                     Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');\r
1734                 }else{\r
1735                     point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);\r
1736                     if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
1737                         this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners);\r
1738                     }\r
1739                 }\r
1740             }\r
1741         }\r
1742         \r
1743     }\r
1744  \r
1745 });\r
1746 \r
1747 Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.ns('Ext.ux.grid');\r
1748 \r
1749 /**\r
1750  * @class Ext.ux.grid.GroupSummary\r
1751  * @extends Ext.util.Observable\r
1752  * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
1753  * updated grouped summary row.\r
1754  */\r
1755 Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
1756     /**\r
1757      * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
1758 summaryRenderer: function(v, params, data){\r
1759     return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
1760 },\r
1761      * </code></pre>\r
1762      */\r
1763     /**\r
1764      * @cfg {String} summaryType (Optional) The type of\r
1765      * calculation to be used for the column.  For options available see\r
1766      * {@link #Calculations}.\r
1767      */\r
1768 \r
1769     constructor : function(config){\r
1770         Ext.apply(this, config);\r
1771         Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
1772     },\r
1773     init : function(grid){\r
1774         this.grid = grid;\r
1775         this.cm = grid.getColumnModel();\r
1776         this.view = grid.getView();\r
1777 \r
1778         var v = this.view;\r
1779         v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
1780 \r
1781         v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
1782         v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
1783         v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
1784         v.afterMethod('onUpdate', this.doUpdate, this);\r
1785         v.afterMethod('onRemove', this.doRemove, this);\r
1786 \r
1787         if(!this.rowTpl){\r
1788             this.rowTpl = new Ext.Template(\r
1789                 '<div class="x-grid3-summary-row" style="{tstyle}">',\r
1790                 '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
1791                     '<tbody><tr>{cells}</tr></tbody>',\r
1792                 '</table></div>'\r
1793             );\r
1794             this.rowTpl.disableFormats = true;\r
1795         }\r
1796         this.rowTpl.compile();\r
1797 \r
1798         if(!this.cellTpl){\r
1799             this.cellTpl = new Ext.Template(\r
1800                 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
1801                 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
1802                 "</td>"\r
1803             );\r
1804             this.cellTpl.disableFormats = true;\r
1805         }\r
1806         this.cellTpl.compile();\r
1807     },\r
1808 \r
1809     /**\r
1810      * Toggle the display of the summary row on/off\r
1811      * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
1812      */\r
1813     toggleSummaries : function(visible){\r
1814         var el = this.grid.getGridEl();\r
1815         if(el){\r
1816             if(visible === undefined){\r
1817                 visible = el.hasClass('x-grid-hide-summary');\r
1818             }\r
1819             el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
1820         }\r
1821     },\r
1822 \r
1823     renderSummary : function(o, cs){\r
1824         cs = cs || this.view.getColumnData();\r
1825         var cfg = this.cm.config;\r
1826 \r
1827         var buf = [], c, p = {}, cf, last = cs.length-1;\r
1828         for(var i = 0, len = cs.length; i < len; i++){\r
1829             c = cs[i];\r
1830             cf = cfg[i];\r
1831             p.id = c.id;\r
1832             p.style = c.style;\r
1833             p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
1834             if(cf.summaryType || cf.summaryRenderer){\r
1835                 p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
1836             }else{\r
1837                 p.value = '';\r
1838             }\r
1839             if(p.value == undefined || p.value === "") p.value = "&#160;";\r
1840             buf[buf.length] = this.cellTpl.apply(p);\r
1841         }\r
1842 \r
1843         return this.rowTpl.apply({\r
1844             tstyle: 'width:'+this.view.getTotalWidth()+';',\r
1845             cells: buf.join('')\r
1846         });\r
1847     },\r
1848 \r
1849     /**\r
1850      * @private\r
1851      * @param {Object} rs\r
1852      * @param {Object} cs\r
1853      */\r
1854     calculate : function(rs, cs){\r
1855         var data = {}, r, c, cfg = this.cm.config, cf;\r
1856         for(var j = 0, jlen = rs.length; j < jlen; j++){\r
1857             r = rs[j];\r
1858             for(var i = 0, len = cs.length; i < len; i++){\r
1859                 c = cs[i];\r
1860                 cf = cfg[i];\r
1861                 if(cf.summaryType){\r
1862                     data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
1863                 }\r
1864             }\r
1865         }\r
1866         return data;\r
1867     },\r
1868 \r
1869     doGroupEnd : function(buf, g, cs, ds, colCount){\r
1870         var data = this.calculate(g.rs, cs);\r
1871         buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
1872     },\r
1873 \r
1874     doWidth : function(col, w, tw){\r
1875         var gs = this.view.getGroups(), s;\r
1876         for(var i = 0, len = gs.length; i < len; i++){\r
1877             s = gs[i].childNodes[2];\r
1878             s.style.width = tw;\r
1879             s.firstChild.style.width = tw;\r
1880             s.firstChild.rows[0].childNodes[col].style.width = w;\r
1881         }\r
1882     },\r
1883 \r
1884     doAllWidths : function(ws, tw){\r
1885         var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
1886         for(var i = 0, len = gs.length; i < len; i++){\r
1887             s = gs[i].childNodes[2];\r
1888             s.style.width = tw;\r
1889             s.firstChild.style.width = tw;\r
1890             cells = s.firstChild.rows[0].childNodes;\r
1891             for(var j = 0; j < wlen; j++){\r
1892                 cells[j].style.width = ws[j];\r
1893             }\r
1894         }\r
1895     },\r
1896 \r
1897     doHidden : function(col, hidden, tw){\r
1898         var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
1899         for(var i = 0, len = gs.length; i < len; i++){\r
1900             s = gs[i].childNodes[2];\r
1901             s.style.width = tw;\r
1902             s.firstChild.style.width = tw;\r
1903             s.firstChild.rows[0].childNodes[col].style.display = display;\r
1904         }\r
1905     },\r
1906 \r
1907     // Note: requires that all (or the first) record in the\r
1908     // group share the same group value. Returns false if the group\r
1909     // could not be found.\r
1910     refreshSummary : function(groupValue){\r
1911         return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
1912     },\r
1913 \r
1914     getSummaryNode : function(gid){\r
1915         var g = Ext.fly(gid, '_gsummary');\r
1916         if(g){\r
1917             return g.down('.x-grid3-summary-row', true);\r
1918         }\r
1919         return null;\r
1920     },\r
1921 \r
1922     refreshSummaryById : function(gid){\r
1923         var g = document.getElementById(gid);\r
1924         if(!g){\r
1925             return false;\r
1926         }\r
1927         var rs = [];\r
1928         this.grid.store.each(function(r){\r
1929             if(r._groupId == gid){\r
1930                 rs[rs.length] = r;\r
1931             }\r
1932         });\r
1933         var cs = this.view.getColumnData();\r
1934         var data = this.calculate(rs, cs);\r
1935         var markup = this.renderSummary({data: data}, cs);\r
1936 \r
1937         var existing = this.getSummaryNode(gid);\r
1938         if(existing){\r
1939             g.removeChild(existing);\r
1940         }\r
1941         Ext.DomHelper.append(g, markup);\r
1942         return true;\r
1943     },\r
1944 \r
1945     doUpdate : function(ds, record){\r
1946         this.refreshSummaryById(record._groupId);\r
1947     },\r
1948 \r
1949     doRemove : function(ds, record, index, isUpdate){\r
1950         if(!isUpdate){\r
1951             this.refreshSummaryById(record._groupId);\r
1952         }\r
1953     },\r
1954 \r
1955     /**\r
1956      * Show a message in the summary row.\r
1957      * <pre><code>\r
1958 grid.on('afteredit', function(){\r
1959     var groupValue = 'Ext Forms: Field Anchoring';\r
1960     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
1961 });\r
1962      * </code></pre>\r
1963      * @param {String} groupValue\r
1964      * @param {String} msg Text to use as innerHTML for the summary row.\r
1965      */\r
1966     showSummaryMsg : function(groupValue, msg){\r
1967         var gid = this.view.getGroupId(groupValue);\r
1968         var node = this.getSummaryNode(gid);\r
1969         if(node){\r
1970             node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
1971         }\r
1972     }\r
1973 });\r
1974 \r
1975 //backwards compat\r
1976 Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
1977 \r
1978 \r
1979 /**\r
1980  * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
1981  * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
1982  * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
1983  * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
1984  * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
1985  * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
1986  * </ul></div>\r
1987  * <p>Custom calculations may be implemented.  An example of\r
1988  * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
1989 // define a custom summary function\r
1990 Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
1991     return v + (record.data.estimate * record.data.rate);\r
1992 };\r
1993  * </code></pre>\r
1994  * @property Calculations\r
1995  */\r
1996 \r
1997 Ext.ux.grid.GroupSummary.Calculations = {\r
1998     'sum' : function(v, record, field){\r
1999         return v + (record.data[field]||0);\r
2000     },\r
2001 \r
2002     'count' : function(v, record, field, data){\r
2003         return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
2004     },\r
2005 \r
2006     'max' : function(v, record, field, data){\r
2007         var v = record.data[field];\r
2008         var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
2009         return v > max ? (data[field+'max'] = v) : max;\r
2010     },\r
2011 \r
2012     'min' : function(v, record, field, data){\r
2013         var v = record.data[field];\r
2014         var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
2015         return v < min ? (data[field+'min'] = v) : min;\r
2016     },\r
2017 \r
2018     'average' : function(v, record, field, data){\r
2019         var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
2020         var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
2021         return t === 0 ? 0 : t / c;\r
2022     }\r
2023 };\r
2024 Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
2025 \r
2026 /**\r
2027  * @class Ext.ux.grid.HybridSummary\r
2028  * @extends Ext.ux.grid.GroupSummary\r
2029  * Adds capability to specify the summary data for the group via json as illustrated here:\r
2030  * <pre><code>\r
2031 {\r
2032     data: [\r
2033         {\r
2034             projectId: 100,     project: 'House',\r
2035             taskId:    112, description: 'Paint',\r
2036             estimate:    6,        rate:     150,\r
2037             due:'06/24/2007'\r
2038         },\r
2039         ...\r
2040     ],\r
2041 \r
2042     summaryData: {\r
2043         'House': {\r
2044             description: 14, estimate: 9,\r
2045                    rate: 99, due: new Date(2009, 6, 29),\r
2046                    cost: 999\r
2047         }\r
2048     }\r
2049 }\r
2050  * </code></pre>\r
2051  *\r
2052  */\r
2053 Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {\r
2054     /**\r
2055      * @private\r
2056      * @param {Object} rs\r
2057      * @param {Object} cs\r
2058      */\r
2059     calculate : function(rs, cs){\r
2060         var gcol = this.view.getGroupField();\r
2061         var gvalue = rs[0].data[gcol];\r
2062         var gdata = this.getSummaryData(gvalue);\r
2063         return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);\r
2064     },\r
2065 \r
2066     /**\r
2067      * <pre><code>\r
2068 grid.on('afteredit', function(){\r
2069     var groupValue = 'Ext Forms: Field Anchoring';\r
2070     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
2071     setTimeout(function(){ // simulate server call\r
2072         // HybridSummary class implements updateSummaryData\r
2073         summary.updateSummaryData(groupValue,\r
2074             // create data object based on configured dataIndex\r
2075             {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});\r
2076     }, 2000);\r
2077 });\r
2078      * </code></pre>\r
2079      * @param {String} groupValue\r
2080      * @param {Object} data data object\r
2081      * @param {Boolean} skipRefresh (Optional) Defaults to false\r
2082      */\r
2083     updateSummaryData : function(groupValue, data, skipRefresh){\r
2084         var json = this.grid.store.reader.jsonData;\r
2085         if(!json.summaryData){\r
2086             json.summaryData = {};\r
2087         }\r
2088         json.summaryData[groupValue] = data;\r
2089         if(!skipRefresh){\r
2090             this.refreshSummary(groupValue);\r
2091         }\r
2092     },\r
2093 \r
2094     /**\r
2095      * Returns the summaryData for the specified groupValue or null.\r
2096      * @param {String} groupValue\r
2097      * @return {Object} summaryData\r
2098      */\r
2099     getSummaryData : function(groupValue){\r
2100         var json = this.grid.store.reader.jsonData;\r
2101         if(json && json.summaryData){\r
2102             return json.summaryData[groupValue];\r
2103         }\r
2104         return null;\r
2105     }\r
2106 });\r
2107 \r
2108 //backwards compat\r
2109 Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;\r
2110 Ext.ux.GroupTab = Ext.extend(Ext.Container, {\r
2111     mainItem: 0,\r
2112     \r
2113     expanded: true,\r
2114     \r
2115     deferredRender: true,\r
2116     \r
2117     activeTab: null,\r
2118     \r
2119     idDelimiter: '__',\r
2120     \r
2121     headerAsText: false,\r
2122     \r
2123     frame: false,\r
2124     \r
2125     hideBorders: true,\r
2126     \r
2127     initComponent: function(config){\r
2128         Ext.apply(this, config);\r
2129         this.frame = false;\r
2130         \r
2131         Ext.ux.GroupTab.superclass.initComponent.call(this);\r
2132         \r
2133         this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');\r
2134         \r
2135         this.setLayout(new Ext.layout.CardLayout({\r
2136             deferredRender: this.deferredRender\r
2137         }));\r
2138         \r
2139         if (!this.stack) {\r
2140             this.stack = Ext.TabPanel.AccessStack();\r
2141         }\r
2142         \r
2143         this.initItems();\r
2144         \r
2145         this.on('beforerender', function(){\r
2146             this.groupEl = this.ownerCt.getGroupEl(this);\r
2147         }, this);\r
2148         \r
2149         this.on('add', this.onAdd, this, {\r
2150             target: this\r
2151         });\r
2152         this.on('remove', this.onRemove, this, {\r
2153             target: this\r
2154         });\r
2155         \r
2156         if (this.mainItem !== undefined) {\r
2157             var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);\r
2158             delete this.mainItem;\r
2159             this.setMainItem(item);\r
2160         }\r
2161     },\r
2162     \r
2163     /**\r
2164      * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which\r
2165      * can return false to cancel the tab change.\r
2166      * @param {String/Panel} tab The id or tab Panel to activate\r
2167      */\r
2168     setActiveTab : function(item){\r
2169         item = this.getComponent(item);\r
2170         if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){\r
2171             return;\r
2172         }\r
2173         if(!this.rendered){\r
2174             this.activeTab = item;\r
2175             return;\r
2176         }\r
2177         if(this.activeTab != item){\r
2178             if(this.activeTab && this.activeTab != this.mainItem){\r
2179                 var oldEl = this.getTabEl(this.activeTab);\r
2180                 if(oldEl){\r
2181                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
2182                 }\r
2183                 this.activeTab.fireEvent('deactivate', this.activeTab);\r
2184             }\r
2185             var el = this.getTabEl(item);\r
2186             Ext.fly(el).addClass('x-grouptabs-strip-active');\r
2187             this.activeTab = item;\r
2188             this.stack.add(item);\r
2189 \r
2190             this.layout.setActiveItem(item);\r
2191             if(this.layoutOnTabChange && item.doLayout){\r
2192                 item.doLayout();\r
2193             }\r
2194             if(this.scrolling){\r
2195                 this.scrollToTab(item, this.animScroll);\r
2196             }\r
2197 \r
2198             item.fireEvent('activate', item);\r
2199             this.fireEvent('tabchange', this, item);\r
2200         }\r
2201     },\r
2202     \r
2203     getTabEl: function(item){\r
2204         if (item == this.mainItem) {\r
2205             return this.groupEl;\r
2206         }\r
2207         return Ext.TabPanel.prototype.getTabEl.call(this, item);\r
2208     },\r
2209     \r
2210     onRender: function(ct, position){\r
2211         Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);\r
2212         \r
2213         this.strip = Ext.fly(this.groupEl).createChild({\r
2214             tag: 'ul',\r
2215             cls: 'x-grouptabs-sub'\r
2216         });\r
2217 \r
2218         this.tooltip = new Ext.ToolTip({\r
2219            target: this.groupEl,\r
2220            delegate: 'a.x-grouptabs-text',\r
2221            trackMouse: true,\r
2222            renderTo: document.body,\r
2223            listeners: {\r
2224                beforeshow: function(tip) {\r
2225                    var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)\r
2226                        ? this.mainItem\r
2227                        : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);\r
2228 \r
2229                    if(!item.tabTip) {\r
2230                        return false;\r
2231                    }\r
2232                    tip.body.dom.innerHTML = item.tabTip;\r
2233                },\r
2234                scope: this\r
2235            }\r
2236         });\r
2237                 \r
2238         if (!this.itemTpl) {\r
2239             var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');\r
2240             tt.disableFormats = true;\r
2241             tt.compile();\r
2242             Ext.ux.GroupTab.prototype.itemTpl = tt;\r
2243         }\r
2244         \r
2245         this.items.each(this.initTab, this);\r
2246     },\r
2247     \r
2248     afterRender: function(){\r
2249         Ext.ux.GroupTab.superclass.afterRender.call(this);\r
2250         \r
2251         if (this.activeTab !== undefined) {\r
2252             var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);\r
2253             delete this.activeTab;\r
2254             this.setActiveTab(item);\r
2255         }\r
2256     },\r
2257     \r
2258     // private\r
2259     initTab: function(item, index){\r
2260         var before = this.strip.dom.childNodes[index];\r
2261         var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);\r
2262         \r
2263         if (item === this.mainItem) {\r
2264             item.tabEl = this.groupEl;\r
2265             p.cls += ' x-grouptabs-main-item';\r
2266         }\r
2267         \r
2268         var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
2269         \r
2270         item.tabEl = item.tabEl || el;\r
2271                 \r
2272         item.on('disable', this.onItemDisabled, this);\r
2273         item.on('enable', this.onItemEnabled, this);\r
2274         item.on('titlechange', this.onItemTitleChanged, this);\r
2275         item.on('iconchange', this.onItemIconChanged, this);\r
2276         item.on('beforeshow', this.onBeforeShowItem, this);\r
2277     },\r
2278     \r
2279     setMainItem: function(item){\r
2280         item = this.getComponent(item);\r
2281         if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {\r
2282             return;\r
2283         }\r
2284         \r
2285         this.mainItem = item;\r
2286     },\r
2287     \r
2288     getMainItem: function(){\r
2289         return this.mainItem || null;\r
2290     },\r
2291     \r
2292     // private\r
2293     onBeforeShowItem: function(item){\r
2294         if (item != this.activeTab) {\r
2295             this.setActiveTab(item);\r
2296             return false;\r
2297         }\r
2298     },\r
2299     \r
2300     // private\r
2301     onAdd: function(gt, item, index){\r
2302         if (this.rendered) {\r
2303             this.initTab.call(this, item, index);\r
2304         }\r
2305     },\r
2306     \r
2307     // private\r
2308     onRemove: function(tp, item){\r
2309         Ext.destroy(Ext.get(this.getTabEl(item)));\r
2310         this.stack.remove(item);\r
2311         item.un('disable', this.onItemDisabled, this);\r
2312         item.un('enable', this.onItemEnabled, this);\r
2313         item.un('titlechange', this.onItemTitleChanged, this);\r
2314         item.un('iconchange', this.onItemIconChanged, this);\r
2315         item.un('beforeshow', this.onBeforeShowItem, this);\r
2316         if (item == this.activeTab) {\r
2317             var next = this.stack.next();\r
2318             if (next) {\r
2319                 this.setActiveTab(next);\r
2320             }\r
2321             else if (this.items.getCount() > 0) {\r
2322                 this.setActiveTab(0);\r
2323             }\r
2324             else {\r
2325                 this.activeTab = null;\r
2326             }\r
2327         }\r
2328     },\r
2329     \r
2330     // private\r
2331     onBeforeAdd: function(item){\r
2332         var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);\r
2333         if (existing) {\r
2334             this.setActiveTab(item);\r
2335             return false;\r
2336         }\r
2337         Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);\r
2338         var es = item.elements;\r
2339         item.elements = es ? es.replace(',header', '') : es;\r
2340         item.border = (item.border === true);\r
2341     },\r
2342     \r
2343     // private\r
2344     onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,\r
2345     onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,\r
2346     \r
2347     // private\r
2348     onItemTitleChanged: function(item){\r
2349         var el = this.getTabEl(item);\r
2350         if (el) {\r
2351             Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;\r
2352         }\r
2353     },\r
2354     \r
2355     //private\r
2356     onItemIconChanged: function(item, iconCls, oldCls){\r
2357         var el = this.getTabEl(item);\r
2358         if (el) {\r
2359             Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);\r
2360         }\r
2361     },\r
2362     \r
2363     beforeDestroy: function(){\r
2364         Ext.TabPanel.prototype.beforeDestroy.call(this);\r
2365         this.tooltip.destroy();\r
2366     }\r
2367 });\r
2368 \r
2369 Ext.reg('grouptab', Ext.ux.GroupTab);\r
2370 Ext.ns('Ext.ux');\r
2371 \r
2372 Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {\r
2373     tabPosition: 'left',\r
2374     \r
2375     alternateColor: false,\r
2376     \r
2377     alternateCls: 'x-grouptabs-panel-alt',\r
2378     \r
2379     defaultType: 'grouptab',\r
2380     \r
2381     deferredRender: false,\r
2382     \r
2383     activeGroup : null,\r
2384     \r
2385     initComponent: function(){\r
2386         Ext.ux.GroupTabPanel.superclass.initComponent.call(this);\r
2387         \r
2388         this.addEvents(\r
2389             'beforegroupchange',\r
2390             'groupchange'\r
2391         );\r
2392         this.elements = 'body,header';\r
2393         this.stripTarget = 'header';\r
2394         \r
2395         this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';\r
2396         \r
2397         this.addClass('x-grouptabs-panel');\r
2398         \r
2399         if (this.tabStyle && this.tabStyle != '') {\r
2400             this.addClass('x-grouptabs-panel-' + this.tabStyle);\r
2401         }\r
2402         \r
2403         if (this.alternateColor) {\r
2404             this.addClass(this.alternateCls);\r
2405         }\r
2406         \r
2407         this.on('beforeadd', function(gtp, item, index){\r
2408             this.initGroup(item, index);\r
2409         });                  \r
2410     },\r
2411     \r
2412     initEvents : function() {\r
2413         this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);\r
2414     },\r
2415         \r
2416     onRender: function(ct, position){\r
2417         Ext.TabPanel.superclass.onRender.call(this, ct, position);\r
2418 \r
2419         if(this.plain){\r
2420             var pos = this.tabPosition == 'top' ? 'header' : 'footer';\r
2421             this[pos].addClass('x-tab-panel-'+pos+'-plain');\r
2422         }\r
2423 \r
2424         var st = this[this.stripTarget];\r
2425 \r
2426         this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{\r
2427             tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});\r
2428 \r
2429         var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);\r
2430         this.strip = new Ext.Element(this.stripWrap.dom.firstChild);\r
2431 \r
2432                 this.header.addClass('x-grouptabs-panel-header');\r
2433                 this.bwrap.addClass('x-grouptabs-bwrap');\r
2434         this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');\r
2435 \r
2436         if (!this.itemTpl) {\r
2437             var tt = new Ext.Template(\r
2438                 '<li class="{cls}" id="{id}">', \r
2439                 '<a class="x-grouptabs-expand" onclick="return false;"></a>', \r
2440                 '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',\r
2441                 '<span>{text}</span></a>', \r
2442                 '</li>'\r
2443             );\r
2444             tt.disableFormats = true;\r
2445             tt.compile();\r
2446             Ext.ux.GroupTabPanel.prototype.itemTpl = tt;\r
2447         }\r
2448 \r
2449         this.items.each(this.initGroup, this);\r
2450     },\r
2451     \r
2452     afterRender: function(){\r
2453         Ext.ux.GroupTabPanel.superclass.afterRender.call(this);\r
2454         \r
2455         this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({\r
2456             cls: 'x-tab-joint'\r
2457         });\r
2458         \r
2459         this.addClass('x-tab-panel-' + this.tabPosition);\r
2460         this.header.setWidth(this.tabWidth);\r
2461         \r
2462         if (this.activeGroup !== undefined) {\r
2463             var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);\r
2464             delete this.activeGroup;\r
2465             this.setActiveGroup(group);\r
2466             group.setActiveTab(group.getMainItem());\r
2467         }\r
2468     },\r
2469 \r
2470     getGroupEl : Ext.TabPanel.prototype.getTabEl,\r
2471         \r
2472     // private\r
2473     findTargets: function(e){\r
2474         var item = null;\r
2475         var itemEl = e.getTarget('li', this.strip);\r
2476         if (itemEl) {\r
2477             item = this.findById(itemEl.id.split(this.idDelimiter)[1]);\r
2478             if (item.disabled) {\r
2479                 return {\r
2480                     expand: null,\r
2481                     item: null,\r
2482                     el: null\r
2483                 };\r
2484             }\r
2485         }\r
2486         return {\r
2487             expand: e.getTarget('.x-grouptabs-expand', this.strip),\r
2488             isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),\r
2489             item: item,\r
2490             el: itemEl\r
2491         };\r
2492     },\r
2493     \r
2494     // private\r
2495     onStripMouseDown: function(e){\r
2496         if (e.button != 0) {\r
2497             return;\r
2498         }\r
2499         e.preventDefault();\r
2500         var t = this.findTargets(e);\r
2501         if (t.expand) {\r
2502             this.toggleGroup(t.el);\r
2503         }\r
2504         else if (t.item) {\r
2505             if(t.isGroup) {\r
2506                 t.item.setActiveTab(t.item.getMainItem());\r
2507             }\r
2508             else {\r
2509                 t.item.ownerCt.setActiveTab(t.item);\r
2510             }\r
2511         }\r
2512     },\r
2513     \r
2514     expandGroup: function(groupEl){\r
2515         if(groupEl.isXType) {\r
2516             groupEl = this.getGroupEl(groupEl);\r
2517         }\r
2518         Ext.fly(groupEl).addClass('x-grouptabs-expanded');\r
2519     },\r
2520     \r
2521     toggleGroup: function(groupEl){\r
2522         if(groupEl.isXType) {\r
2523             groupEl = this.getGroupEl(groupEl);\r
2524         }        \r
2525         Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');\r
2526                 this.syncTabJoint();\r
2527     },    \r
2528     \r
2529     syncTabJoint: function(groupEl){\r
2530         if (!this.tabJoint) {\r
2531             return;\r
2532         }\r
2533         \r
2534         groupEl = groupEl || this.getGroupEl(this.activeGroup);\r
2535         if(groupEl) {\r
2536             this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); \r
2537                         \r
2538             var y = Ext.isGecko2 ? 0 : 1;\r
2539             if (this.tabPosition == 'left'){\r
2540                 this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);\r
2541             }\r
2542             else {\r
2543                 this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);\r
2544             }           \r
2545         }\r
2546         else {\r
2547             this.tabJoint.hide();\r
2548         }\r
2549     },\r
2550     \r
2551     getActiveTab : function() {\r
2552         if(!this.activeGroup) return null;\r
2553         return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null;  \r
2554     },\r
2555     \r
2556     onResize: function(){\r
2557         Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);\r
2558         this.syncTabJoint();\r
2559     },\r
2560     \r
2561     createCorner: function(el, pos){\r
2562         return Ext.fly(el).createChild({\r
2563             cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos\r
2564         });\r
2565     },\r
2566     \r
2567     initGroup: function(group, index){\r
2568         var before = this.strip.dom.childNodes[index];        \r
2569         var p = this.getTemplateArgs(group);\r
2570         if (index === 0) {\r
2571             p.cls += ' x-tab-first';\r
2572         }\r
2573         p.cls += ' x-grouptabs-main';\r
2574         p.text = group.getMainItem().title;\r
2575         \r
2576         var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
2577         \r
2578         var tl = this.createCorner(el, 'top-' + this.tabPosition);\r
2579         var bl = this.createCorner(el, 'bottom-' + this.tabPosition);\r
2580 \r
2581         if (group.expanded) {\r
2582             this.expandGroup(el);\r
2583         }\r
2584 \r
2585         if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){\r
2586             bl.setLeft('-10px');\r
2587             bl.setBottom('-5px');\r
2588             tl.setLeft('-10px');\r
2589             tl.setTop('-5px');\r
2590         }\r
2591 \r
2592         this.mon(group, 'changemainitem', this.onGroupChangeMainItem, this);\r
2593         this.mon(group, 'beforetabchange', this.onGroupBeforeTabChange, this);\r
2594     },\r
2595     \r
2596     setActiveGroup : function(group) {\r
2597         group = this.getComponent(group);\r
2598         if(!group || this.fireEvent('beforegroupchange', this, group, this.activeGroup) === false){\r
2599             return;\r
2600         }\r
2601         if(!this.rendered){\r
2602             this.activeGroup = group;\r
2603             return;\r
2604         }\r
2605         if(this.activeGroup != group){\r
2606             if(this.activeGroup){\r
2607                 var oldEl = this.getGroupEl(this.activeGroup);\r
2608                 if(oldEl){\r
2609                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
2610                 }\r
2611                 this.activeGroup.fireEvent('deactivate', this.activeTab);\r
2612             }\r
2613 \r
2614             var groupEl = this.getGroupEl(group);\r
2615             Ext.fly(groupEl).addClass('x-grouptabs-strip-active');\r
2616                         \r
2617             this.activeGroup = group;\r
2618             this.stack.add(group);\r
2619 \r
2620             this.layout.setActiveItem(group);\r
2621             this.syncTabJoint(groupEl);\r
2622 \r
2623             group.fireEvent('activate', group);\r
2624             this.fireEvent('groupchange', this, group);\r
2625         }        \r
2626     },\r
2627     \r
2628     onGroupBeforeTabChange: function(group, newTab, oldTab){\r
2629         if(group !== this.activeGroup || newTab !== oldTab) {\r
2630             this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');\r
2631         } \r
2632         \r
2633         this.expandGroup(this.getGroupEl(group));\r
2634         this.setActiveGroup(group);\r
2635     },\r
2636     \r
2637     getFrameHeight: function(){\r
2638         var h = this.el.getFrameWidth('tb');\r
2639         h += (this.tbar ? this.tbar.getHeight() : 0) +\r
2640         (this.bbar ? this.bbar.getHeight() : 0);\r
2641         \r
2642         return h;\r
2643     },\r
2644     \r
2645     adjustBodyWidth: function(w){\r
2646         return w - this.tabWidth;\r
2647     }\r
2648 });\r
2649 \r
2650 Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/*\r
2651  * Note that this control will most likely remain as an example, and not as a core Ext form\r
2652  * control.  However, the API will be changing in a future release and so should not yet be\r
2653  * treated as a final, stable API at this time.\r
2654  */\r
2655 \r
2656 /**\r
2657  * @class Ext.ux.form.ItemSelector\r
2658  * @extends Ext.form.Field\r
2659  * A control that allows selection of between two Ext.ux.form.MultiSelect controls.\r
2660  *\r
2661  *  @history\r
2662  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
2663  *\r
2664  * @constructor\r
2665  * Create a new ItemSelector\r
2666  * @param {Object} config Configuration options\r
2667  * @xtype itemselector \r
2668  */\r
2669 Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {\r
2670     hideNavIcons:false,\r
2671     imagePath:"",\r
2672     iconUp:"up2.gif",\r
2673     iconDown:"down2.gif",\r
2674     iconLeft:"left2.gif",\r
2675     iconRight:"right2.gif",\r
2676     iconTop:"top2.gif",\r
2677     iconBottom:"bottom2.gif",\r
2678     drawUpIcon:true,\r
2679     drawDownIcon:true,\r
2680     drawLeftIcon:true,\r
2681     drawRightIcon:true,\r
2682     drawTopIcon:true,\r
2683     drawBotIcon:true,\r
2684     delimiter:',',\r
2685     bodyStyle:null,\r
2686     border:false,\r
2687     defaultAutoCreate:{tag: "div"},\r
2688     /**\r
2689      * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)\r
2690      */\r
2691     multiselects:null,\r
2692 \r
2693     initComponent: function(){\r
2694         Ext.ux.form.ItemSelector.superclass.initComponent.call(this);\r
2695         this.addEvents({\r
2696             'rowdblclick' : true,\r
2697             'change' : true\r
2698         });\r
2699     },\r
2700 \r
2701     onRender: function(ct, position){\r
2702         Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);\r
2703 \r
2704         // Internal default configuration for both multiselects\r
2705         var msConfig = [{\r
2706             legend: 'Available',\r
2707             draggable: true,\r
2708             droppable: true,\r
2709             width: 100,\r
2710             height: 100\r
2711         },{\r
2712             legend: 'Selected',\r
2713             droppable: true,\r
2714             draggable: true,\r
2715             width: 100,\r
2716             height: 100\r
2717         }];\r
2718 \r
2719         this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));\r
2720         this.fromMultiselect.on('dblclick', this.onRowDblClick, this);\r
2721 \r
2722         this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));\r
2723         this.toMultiselect.on('dblclick', this.onRowDblClick, this);\r
2724 \r
2725         var p = new Ext.Panel({\r
2726             bodyStyle:this.bodyStyle,\r
2727             border:this.border,\r
2728             layout:"table",\r
2729             layoutConfig:{columns:3}\r
2730         });\r
2731 \r
2732         p.add(this.fromMultiselect);\r
2733         var icons = new Ext.Panel({header:false});\r
2734         p.add(icons);\r
2735         p.add(this.toMultiselect);\r
2736         p.render(this.el);\r
2737         icons.el.down('.'+icons.bwrapCls).remove();\r
2738 \r
2739         // ICON HELL!!!\r
2740         if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")\r
2741             this.imagePath+="/";\r
2742         this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');\r
2743         this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');\r
2744         this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');\r
2745         this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');\r
2746         this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');\r
2747         this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');\r
2748         var el=icons.getEl();\r
2749         this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});\r
2750         el.createChild({tag: 'br'});\r
2751         this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});\r
2752         el.createChild({tag: 'br'});\r
2753         this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});\r
2754         el.createChild({tag: 'br'});\r
2755         this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});\r
2756         el.createChild({tag: 'br'});\r
2757         this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});\r
2758         el.createChild({tag: 'br'});\r
2759         this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});\r
2760         this.toTopIcon.on('click', this.toTop, this);\r
2761         this.upIcon.on('click', this.up, this);\r
2762         this.downIcon.on('click', this.down, this);\r
2763         this.toBottomIcon.on('click', this.toBottom, this);\r
2764         this.addIcon.on('click', this.fromTo, this);\r
2765         this.removeIcon.on('click', this.toFrom, this);\r
2766         if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }\r
2767         if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }\r
2768         if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }\r
2769         if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }\r
2770         if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }\r
2771         if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }\r
2772 \r
2773         var tb = p.body.first();\r
2774         this.el.setWidth(p.body.first().getWidth());\r
2775         p.body.removeClass();\r
2776 \r
2777         this.hiddenName = this.name;\r
2778         var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};\r
2779         this.hiddenField = this.el.createChild(hiddenTag);\r
2780     },\r
2781     \r
2782     doLayout: function(){\r
2783         if(this.rendered){\r
2784             this.fromMultiselect.fs.doLayout();\r
2785             this.toMultiselect.fs.doLayout();\r
2786         }\r
2787     },\r
2788 \r
2789     afterRender: function(){\r
2790         Ext.ux.form.ItemSelector.superclass.afterRender.call(this);\r
2791 \r
2792         this.toStore = this.toMultiselect.store;\r
2793         this.toStore.on('add', this.valueChanged, this);\r
2794         this.toStore.on('remove', this.valueChanged, this);\r
2795         this.toStore.on('load', this.valueChanged, this);\r
2796         this.valueChanged(this.toStore);\r
2797     },\r
2798 \r
2799     toTop : function() {\r
2800         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
2801         var records = [];\r
2802         if (selectionsArray.length > 0) {\r
2803             selectionsArray.sort();\r
2804             for (var i=0; i<selectionsArray.length; i++) {\r
2805                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
2806                 records.push(record);\r
2807             }\r
2808             selectionsArray = [];\r
2809             for (var i=records.length-1; i>-1; i--) {\r
2810                 record = records[i];\r
2811                 this.toMultiselect.view.store.remove(record);\r
2812                 this.toMultiselect.view.store.insert(0, record);\r
2813                 selectionsArray.push(((records.length - 1) - i));\r
2814             }\r
2815         }\r
2816         this.toMultiselect.view.refresh();\r
2817         this.toMultiselect.view.select(selectionsArray);\r
2818     },\r
2819 \r
2820     toBottom : function() {\r
2821         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
2822         var records = [];\r
2823         if (selectionsArray.length > 0) {\r
2824             selectionsArray.sort();\r
2825             for (var i=0; i<selectionsArray.length; i++) {\r
2826                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
2827                 records.push(record);\r
2828             }\r
2829             selectionsArray = [];\r
2830             for (var i=0; i<records.length; i++) {\r
2831                 record = records[i];\r
2832                 this.toMultiselect.view.store.remove(record);\r
2833                 this.toMultiselect.view.store.add(record);\r
2834                 selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));\r
2835             }\r
2836         }\r
2837         this.toMultiselect.view.refresh();\r
2838         this.toMultiselect.view.select(selectionsArray);\r
2839     },\r
2840 \r
2841     up : function() {\r
2842         var record = null;\r
2843         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
2844         selectionsArray.sort();\r
2845         var newSelectionsArray = [];\r
2846         if (selectionsArray.length > 0) {\r
2847             for (var i=0; i<selectionsArray.length; i++) {\r
2848                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
2849                 if ((selectionsArray[i] - 1) >= 0) {\r
2850                     this.toMultiselect.view.store.remove(record);\r
2851                     this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);\r
2852                     newSelectionsArray.push(selectionsArray[i] - 1);\r
2853                 }\r
2854             }\r
2855             this.toMultiselect.view.refresh();\r
2856             this.toMultiselect.view.select(newSelectionsArray);\r
2857         }\r
2858     },\r
2859 \r
2860     down : function() {\r
2861         var record = null;\r
2862         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
2863         selectionsArray.sort();\r
2864         selectionsArray.reverse();\r
2865         var newSelectionsArray = [];\r
2866         if (selectionsArray.length > 0) {\r
2867             for (var i=0; i<selectionsArray.length; i++) {\r
2868                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
2869                 if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {\r
2870                     this.toMultiselect.view.store.remove(record);\r
2871                     this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);\r
2872                     newSelectionsArray.push(selectionsArray[i] + 1);\r
2873                 }\r
2874             }\r
2875             this.toMultiselect.view.refresh();\r
2876             this.toMultiselect.view.select(newSelectionsArray);\r
2877         }\r
2878     },\r
2879 \r
2880     fromTo : function() {\r
2881         var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();\r
2882         var records = [];\r
2883         if (selectionsArray.length > 0) {\r
2884             for (var i=0; i<selectionsArray.length; i++) {\r
2885                 record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);\r
2886                 records.push(record);\r
2887             }\r
2888             if(!this.allowDup)selectionsArray = [];\r
2889             for (var i=0; i<records.length; i++) {\r
2890                 record = records[i];\r
2891                 if(this.allowDup){\r
2892                     var x=new Ext.data.Record();\r
2893                     record.id=x.id;\r
2894                     delete x;\r
2895                     this.toMultiselect.view.store.add(record);\r
2896                 }else{\r
2897                     this.fromMultiselect.view.store.remove(record);\r
2898                     this.toMultiselect.view.store.add(record);\r
2899                     selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));\r
2900                 }\r
2901             }\r
2902         }\r
2903         this.toMultiselect.view.refresh();\r
2904         this.fromMultiselect.view.refresh();\r
2905         var si = this.toMultiselect.store.sortInfo;\r
2906         if(si){\r
2907             this.toMultiselect.store.sort(si.field, si.direction);\r
2908         }\r
2909         this.toMultiselect.view.select(selectionsArray);\r
2910     },\r
2911 \r
2912     toFrom : function() {\r
2913         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
2914         var records = [];\r
2915         if (selectionsArray.length > 0) {\r
2916             for (var i=0; i<selectionsArray.length; i++) {\r
2917                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
2918                 records.push(record);\r
2919             }\r
2920             selectionsArray = [];\r
2921             for (var i=0; i<records.length; i++) {\r
2922                 record = records[i];\r
2923                 this.toMultiselect.view.store.remove(record);\r
2924                 if(!this.allowDup){\r
2925                     this.fromMultiselect.view.store.add(record);\r
2926                     selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));\r
2927                 }\r
2928             }\r
2929         }\r
2930         this.fromMultiselect.view.refresh();\r
2931         this.toMultiselect.view.refresh();\r
2932         var si = this.fromMultiselect.store.sortInfo;\r
2933         if (si){\r
2934             this.fromMultiselect.store.sort(si.field, si.direction);\r
2935         }\r
2936         this.fromMultiselect.view.select(selectionsArray);\r
2937     },\r
2938 \r
2939     valueChanged: function(store) {\r
2940         var record = null;\r
2941         var values = [];\r
2942         for (var i=0; i<store.getCount(); i++) {\r
2943             record = store.getAt(i);\r
2944             values.push(record.get(this.toMultiselect.valueField));\r
2945         }\r
2946         this.hiddenField.dom.value = values.join(this.delimiter);\r
2947         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
2948     },\r
2949 \r
2950     getValue : function() {\r
2951         return this.hiddenField.dom.value;\r
2952     },\r
2953 \r
2954     onRowDblClick : function(vw, index, node, e) {\r
2955         if (vw == this.toMultiselect.view){\r
2956             this.toFrom();\r
2957         } else if (vw == this.fromMultiselect.view) {\r
2958             this.fromTo();\r
2959         }\r
2960         return this.fireEvent('rowdblclick', vw, index, node, e);\r
2961     },\r
2962 \r
2963     reset: function(){\r
2964         range = this.toMultiselect.store.getRange();\r
2965         this.toMultiselect.store.removeAll();\r
2966         this.fromMultiselect.store.add(range);\r
2967         var si = this.fromMultiselect.store.sortInfo;\r
2968         if (si){\r
2969             this.fromMultiselect.store.sort(si.field, si.direction);\r
2970         }\r
2971         this.valueChanged(this.toMultiselect.store);\r
2972     }\r
2973 });\r
2974 \r
2975 Ext.reg('itemselector', Ext.ux.form.ItemSelector);\r
2976 \r
2977 //backwards compat\r
2978 Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;\r
2979 Ext.ns('Ext.ux.form');\r
2980 \r
2981 /**\r
2982  * @class Ext.ux.form.MultiSelect\r
2983  * @extends Ext.form.Field\r
2984  * A control that allows selection and form submission of multiple list items.\r
2985  *\r
2986  *  @history\r
2987  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
2988  *    2008-06-19 bpm Docs and demo code clean up\r
2989  *\r
2990  * @constructor\r
2991  * Create a new MultiSelect\r
2992  * @param {Object} config Configuration options\r
2993  * @xtype multiselect \r
2994  */\r
2995 Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {\r
2996     /**\r
2997      * @cfg {String} legend Wraps the object with a fieldset and specified legend.\r
2998      */\r
2999     /**\r
3000      * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.\r
3001      */\r
3002     /**\r
3003      * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).\r
3004      */\r
3005     /**\r
3006      * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).\r
3007      */\r
3008     /**\r
3009      * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).\r
3010      */\r
3011     ddReorder:false,\r
3012     /**\r
3013      * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a\r
3014      * toolbar config, or an array of buttons/button configs to be added to the toolbar.\r
3015      */\r
3016     /**\r
3017      * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled\r
3018      * (use for lists which are sorted, defaults to false).\r
3019      */\r
3020     appendOnly:false,\r
3021     /**\r
3022      * @cfg {Number} width Width in pixels of the control (defaults to 100).\r
3023      */\r
3024     width:100,\r
3025     /**\r
3026      * @cfg {Number} height Height in pixels of the control (defaults to 100).\r
3027      */\r
3028     height:100,\r
3029     /**\r
3030      * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).\r
3031      */\r
3032     displayField:0,\r
3033     /**\r
3034      * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).\r
3035      */\r
3036     valueField:1,\r
3037     /**\r
3038      * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no\r
3039      * selection (defaults to true).\r
3040      */\r
3041     allowBlank:true,\r
3042     /**\r
3043      * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).\r
3044      */\r
3045     minSelections:0,\r
3046     /**\r
3047      * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).\r
3048      */\r
3049     maxSelections:Number.MAX_VALUE,\r
3050     /**\r
3051      * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as\r
3052      * {@link Ext.form.TextField#blankText}.\r
3053      */\r
3054     blankText:Ext.form.TextField.prototype.blankText,\r
3055     /**\r
3056      * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}\r
3057      * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.\r
3058      */\r
3059     minSelectionsText:'Minimum {0} item(s) required',\r
3060     /**\r
3061      * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}\r
3062      * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.\r
3063      */\r
3064     maxSelectionsText:'Maximum {0} item(s) allowed',\r
3065     /**\r
3066      * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values\r
3067      * (defaults to ',').\r
3068      */\r
3069     delimiter:',',\r
3070     /**\r
3071      * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).\r
3072      * Acceptable values for this property are:\r
3073      * <div class="mdetail-params"><ul>\r
3074      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>\r
3075      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.\r
3076      * <div class="mdetail-params"><ul>\r
3077      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">\r
3078      * A 1-dimensional array will automatically be expanded (each array item will be the combo\r
3079      * {@link #valueField value} and {@link #displayField text})</div></li>\r
3080      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">\r
3081      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo\r
3082      * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.\r
3083      * </div></li></ul></div></li></ul></div>\r
3084      */\r
3085 \r
3086     // private\r
3087     defaultAutoCreate : {tag: "div"},\r
3088 \r
3089     // private\r
3090     initComponent: function(){\r
3091         Ext.ux.form.MultiSelect.superclass.initComponent.call(this);\r
3092 \r
3093         if(Ext.isArray(this.store)){\r
3094             if (Ext.isArray(this.store[0])){\r
3095                 this.store = new Ext.data.ArrayStore({\r
3096                     fields: ['value','text'],\r
3097                     data: this.store\r
3098                 });\r
3099                 this.valueField = 'value';\r
3100             }else{\r
3101                 this.store = new Ext.data.ArrayStore({\r
3102                     fields: ['text'],\r
3103                     data: this.store,\r
3104                     expandData: true\r
3105                 });\r
3106                 this.valueField = 'text';\r
3107             }\r
3108             this.displayField = 'text';\r
3109         } else {\r
3110             this.store = Ext.StoreMgr.lookup(this.store);\r
3111         }\r
3112 \r
3113         this.addEvents({\r
3114             'dblclick' : true,\r
3115             'click' : true,\r
3116             'change' : true,\r
3117             'drop' : true\r
3118         });\r
3119     },\r
3120 \r
3121     // private\r
3122     onRender: function(ct, position){\r
3123         Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);\r
3124 \r
3125         var fs = this.fs = new Ext.form.FieldSet({\r
3126             renderTo: this.el,\r
3127             title: this.legend,\r
3128             height: this.height,\r
3129             width: this.width,\r
3130             style: "padding:0;",\r
3131             tbar: this.tbar,\r
3132             bodyStyle: 'overflow: auto;'\r
3133         });\r
3134 \r
3135         this.view = new Ext.ListView({\r
3136             multiSelect: true,\r
3137             store: this.store,\r
3138             columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],\r
3139             hideHeaders: true\r
3140         });\r
3141 \r
3142         fs.add(this.view);\r
3143 \r
3144         this.view.on('click', this.onViewClick, this);\r
3145         this.view.on('beforeclick', this.onViewBeforeClick, this);\r
3146         this.view.on('dblclick', this.onViewDblClick, this);\r
3147 \r
3148         this.hiddenName = this.name || Ext.id();\r
3149         var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };\r
3150         this.hiddenField = this.el.createChild(hiddenTag);\r
3151         this.hiddenField.dom.disabled = this.hiddenName != this.name;\r
3152         fs.doLayout();\r
3153     },\r
3154 \r
3155     // private\r
3156     afterRender: function(){\r
3157         Ext.ux.form.MultiSelect.superclass.afterRender.call(this);\r
3158 \r
3159         if (this.ddReorder && !this.dragGroup && !this.dropGroup){\r
3160             this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();\r
3161         }\r
3162 \r
3163         if (this.draggable || this.dragGroup){\r
3164             this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {\r
3165                 ddGroup: this.dragGroup\r
3166             });\r
3167         }\r
3168         if (this.droppable || this.dropGroup){\r
3169             this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {\r
3170                 ddGroup: this.dropGroup\r
3171             });\r
3172         }\r
3173     },\r
3174 \r
3175     // private\r
3176     onViewClick: function(vw, index, node, e) {\r
3177         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
3178         this.hiddenField.dom.value = this.getValue();\r
3179         this.fireEvent('click', this, e);\r
3180         this.validate();\r
3181     },\r
3182 \r
3183     // private\r
3184     onViewBeforeClick: function(vw, index, node, e) {\r
3185         if (this.disabled) {return false;}\r
3186     },\r
3187 \r
3188     // private\r
3189     onViewDblClick : function(vw, index, node, e) {\r
3190         return this.fireEvent('dblclick', vw, index, node, e);\r
3191     },\r
3192 \r
3193     /**\r
3194      * Returns an array of data values for the selected items in the list. The values will be separated\r
3195      * by {@link #delimiter}.\r
3196      * @return {Array} value An array of string data values\r
3197      */\r
3198     getValue: function(valueField){\r
3199         var returnArray = [];\r
3200         var selectionsArray = this.view.getSelectedIndexes();\r
3201         if (selectionsArray.length == 0) {return '';}\r
3202         for (var i=0; i<selectionsArray.length; i++) {\r
3203             returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));\r
3204         }\r
3205         return returnArray.join(this.delimiter);\r
3206     },\r
3207 \r
3208     /**\r
3209      * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.\r
3210      * @param {String/Array} values The values to set\r
3211      */\r
3212     setValue: function(values) {\r
3213         var index;\r
3214         var selections = [];\r
3215         this.view.clearSelections();\r
3216         this.hiddenField.dom.value = '';\r
3217 \r
3218         if (!values || (values == '')) { return; }\r
3219 \r
3220         if (!Ext.isArray(values)) { values = values.split(this.delimiter); }\r
3221         for (var i=0; i<values.length; i++) {\r
3222             index = this.view.store.indexOf(this.view.store.query(this.valueField,\r
3223                 new RegExp('^' + values[i] + '$', "i")).itemAt(0));\r
3224             selections.push(index);\r
3225         }\r
3226         this.view.select(selections);\r
3227         this.hiddenField.dom.value = this.getValue();\r
3228         this.validate();\r
3229     },\r
3230 \r
3231     // inherit docs\r
3232     reset : function() {\r
3233         this.setValue('');\r
3234     },\r
3235 \r
3236     // inherit docs\r
3237     getRawValue: function(valueField) {\r
3238         var tmp = this.getValue(valueField);\r
3239         if (tmp.length) {\r
3240             tmp = tmp.split(this.delimiter);\r
3241         }\r
3242         else {\r
3243             tmp = [];\r
3244         }\r
3245         return tmp;\r
3246     },\r
3247 \r
3248     // inherit docs\r
3249     setRawValue: function(values){\r
3250         setValue(values);\r
3251     },\r
3252 \r
3253     // inherit docs\r
3254     validateValue : function(value){\r
3255         if (value.length < 1) { // if it has no value\r
3256              if (this.allowBlank) {\r
3257                  this.clearInvalid();\r
3258                  return true;\r
3259              } else {\r
3260                  this.markInvalid(this.blankText);\r
3261                  return false;\r
3262              }\r
3263         }\r
3264         if (value.length < this.minSelections) {\r
3265             this.markInvalid(String.format(this.minSelectionsText, this.minSelections));\r
3266             return false;\r
3267         }\r
3268         if (value.length > this.maxSelections) {\r
3269             this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));\r
3270             return false;\r
3271         }\r
3272         return true;\r
3273     },\r
3274 \r
3275     // inherit docs\r
3276     disable: function(){\r
3277         this.disabled = true;\r
3278         this.hiddenField.dom.disabled = true;\r
3279         this.fs.disable();\r
3280     },\r
3281 \r
3282     // inherit docs\r
3283     enable: function(){\r
3284         this.disabled = false;\r
3285         this.hiddenField.dom.disabled = false;\r
3286         this.fs.enable();\r
3287     },\r
3288 \r
3289     // inherit docs\r
3290     destroy: function(){\r
3291         Ext.destroy(this.fs, this.dragZone, this.dropZone);\r
3292         Ext.ux.form.MultiSelect.superclass.destroy.call(this);\r
3293     }\r
3294 });\r
3295 \r
3296 \r
3297 Ext.reg('multiselect', Ext.ux.form.MultiSelect);\r
3298 \r
3299 //backwards compat\r
3300 Ext.ux.Multiselect = Ext.ux.form.MultiSelect;\r
3301 \r
3302 \r
3303 Ext.ux.form.MultiSelect.DragZone = function(ms, config){\r
3304     this.ms = ms;\r
3305     this.view = ms.view;\r
3306     var ddGroup = config.ddGroup || 'MultiselectDD';\r
3307     var dd;\r
3308     if (Ext.isArray(ddGroup)){\r
3309         dd = ddGroup.shift();\r
3310     } else {\r
3311         dd = ddGroup;\r
3312         ddGroup = null;\r
3313     }\r
3314     Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
3315     this.setDraggable(ddGroup);\r
3316 };\r
3317 \r
3318 Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {\r
3319     onInitDrag : function(x, y){\r
3320         var el = Ext.get(this.dragData.ddel.cloneNode(true));\r
3321         this.proxy.update(el.dom);\r
3322         el.setWidth(el.child('em').getWidth());\r
3323         this.onStartDrag(x, y);\r
3324         return true;\r
3325     },\r
3326     \r
3327     // private\r
3328     collectSelection: function(data) {\r
3329         data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();\r
3330         var i = 0;\r
3331         this.view.store.each(function(rec){\r
3332             if (this.view.isSelected(i)) {\r
3333                 var n = this.view.getNode(i);\r
3334                 var dragNode = n.cloneNode(true);\r
3335                 dragNode.id = Ext.id();\r
3336                 data.ddel.appendChild(dragNode);\r
3337                 data.records.push(this.view.store.getAt(i));\r
3338                 data.viewNodes.push(n);\r
3339             }\r
3340             i++;\r
3341         }, this);\r
3342     },\r
3343 \r
3344     // override\r
3345     onEndDrag: function(data, e) {\r
3346         var d = Ext.get(this.dragData.ddel);\r
3347         if (d && d.hasClass("multi-proxy")) {\r
3348             d.remove();\r
3349         }\r
3350     },\r
3351 \r
3352     // override\r
3353     getDragData: function(e){\r
3354         var target = this.view.findItemFromChild(e.getTarget());\r
3355         if(target) {\r
3356             if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {\r
3357                 this.view.select(target);\r
3358                 this.ms.setValue(this.ms.getValue());\r
3359             }\r
3360             if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;\r
3361             var dragData = {\r
3362                 sourceView: this.view,\r
3363                 viewNodes: [],\r
3364                 records: []\r
3365             };\r
3366             if (this.view.getSelectionCount() == 1) {\r
3367                 var i = this.view.getSelectedIndexes()[0];\r
3368                 var n = this.view.getNode(i);\r
3369                 dragData.viewNodes.push(dragData.ddel = n);\r
3370                 dragData.records.push(this.view.store.getAt(i));\r
3371                 dragData.repairXY = Ext.fly(n).getXY();\r
3372             } else {\r
3373                 dragData.ddel = document.createElement('div');\r
3374                 dragData.ddel.className = 'multi-proxy';\r
3375                 this.collectSelection(dragData);\r
3376             }\r
3377             return dragData;\r
3378         }\r
3379         return false;\r
3380     },\r
3381 \r
3382     // override the default repairXY.\r
3383     getRepairXY : function(e){\r
3384         return this.dragData.repairXY;\r
3385     },\r
3386 \r
3387     // private\r
3388     setDraggable: function(ddGroup){\r
3389         if (!ddGroup) return;\r
3390         if (Ext.isArray(ddGroup)) {\r
3391             Ext.each(ddGroup, this.setDraggable, this);\r
3392             return;\r
3393         }\r
3394         this.addToGroup(ddGroup);\r
3395     }\r
3396 });\r
3397 \r
3398 Ext.ux.form.MultiSelect.DropZone = function(ms, config){\r
3399     this.ms = ms;\r
3400     this.view = ms.view;\r
3401     var ddGroup = config.ddGroup || 'MultiselectDD';\r
3402     var dd;\r
3403     if (Ext.isArray(ddGroup)){\r
3404         dd = ddGroup.shift();\r
3405     } else {\r
3406         dd = ddGroup;\r
3407         ddGroup = null;\r
3408     }\r
3409     Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
3410     this.setDroppable(ddGroup);\r
3411 };\r
3412 \r
3413 Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {\r
3414     /**\r
3415          * Part of the Ext.dd.DropZone interface. If no target node is found, the\r
3416          * whole Element becomes the target, and this causes the drop gesture to append.\r
3417          */\r
3418     getTargetFromEvent : function(e) {\r
3419         var target = e.getTarget();\r
3420         return target;\r
3421     },\r
3422 \r
3423     // private\r
3424     getDropPoint : function(e, n, dd){\r
3425         if (n == this.ms.fs.body.dom) { return "below"; }\r
3426         var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;\r
3427         var c = t + (b - t) / 2;\r
3428         var y = Ext.lib.Event.getPageY(e);\r
3429         if(y <= c) {\r
3430             return "above";\r
3431         }else{\r
3432             return "below";\r
3433         }\r
3434     },\r
3435 \r
3436     // private\r
3437     isValidDropPoint: function(pt, n, data) {\r
3438         if (!data.viewNodes || (data.viewNodes.length != 1)) {\r
3439             return true;\r
3440         }\r
3441         var d = data.viewNodes[0];\r
3442         if (d == n) {\r
3443             return false;\r
3444         }\r
3445         if ((pt == "below") && (n.nextSibling == d)) {\r
3446             return false;\r
3447         }\r
3448         if ((pt == "above") && (n.previousSibling == d)) {\r
3449             return false;\r
3450         }\r
3451         return true;\r
3452     },\r
3453 \r
3454     // override\r
3455     onNodeEnter : function(n, dd, e, data){\r
3456         return false;\r
3457     },\r
3458 \r
3459     // override\r
3460     onNodeOver : function(n, dd, e, data){\r
3461         var dragElClass = this.dropNotAllowed;\r
3462         var pt = this.getDropPoint(e, n, dd);\r
3463         if (this.isValidDropPoint(pt, n, data)) {\r
3464             if (this.ms.appendOnly) {\r
3465                 return "x-tree-drop-ok-below";\r
3466             }\r
3467 \r
3468             // set the insert point style on the target node\r
3469             if (pt) {\r
3470                 var targetElClass;\r
3471                 if (pt == "above"){\r
3472                     dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";\r
3473                     targetElClass = "x-view-drag-insert-above";\r
3474                 } else {\r
3475                     dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";\r
3476                     targetElClass = "x-view-drag-insert-below";\r
3477                 }\r
3478                 if (this.lastInsertClass != targetElClass){\r
3479                     Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);\r
3480                     this.lastInsertClass = targetElClass;\r
3481                 }\r
3482             }\r
3483         }\r
3484         return dragElClass;\r
3485     },\r
3486 \r
3487     // private\r
3488     onNodeOut : function(n, dd, e, data){\r
3489         this.removeDropIndicators(n);\r
3490     },\r
3491 \r
3492     // private\r
3493     onNodeDrop : function(n, dd, e, data){\r
3494         if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {\r
3495             return false;\r
3496         }\r
3497         var pt = this.getDropPoint(e, n, dd);\r
3498         if (n != this.ms.fs.body.dom)\r
3499             n = this.view.findItemFromChild(n);\r
3500         var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n);\r
3501         if (pt == "below") {\r
3502             insertAt++;\r
3503         }\r
3504 \r
3505         var dir = false;\r
3506 \r
3507         // Validate if dragging within the same MultiSelect\r
3508         if (data.sourceView == this.view) {\r
3509             // If the first element to be inserted below is the target node, remove it\r
3510             if (pt == "below") {\r
3511                 if (data.viewNodes[0] == n) {\r
3512                     data.viewNodes.shift();\r
3513                 }\r
3514             } else {  // If the last element to be inserted above is the target node, remove it\r
3515                 if (data.viewNodes[data.viewNodes.length - 1] == n) {\r
3516                     data.viewNodes.pop();\r
3517                 }\r
3518             }\r
3519 \r
3520             // Nothing to drop...\r
3521             if (!data.viewNodes.length) {\r
3522                 return false;\r
3523             }\r
3524 \r
3525             // If we are moving DOWN, then because a store.remove() takes place first,\r
3526             // the insertAt must be decremented.\r
3527             if (insertAt > this.view.store.indexOf(data.records[0])) {\r
3528                 dir = 'down';\r
3529                 insertAt--;\r
3530             }\r
3531         }\r
3532 \r
3533         for (var i = 0; i < data.records.length; i++) {\r
3534             var r = data.records[i];\r
3535             if (data.sourceView) {\r
3536                 data.sourceView.store.remove(r);\r
3537             }\r
3538             this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);\r
3539             var si = this.view.store.sortInfo;\r
3540             if(si){\r
3541                 this.view.store.sort(si.field, si.direction);\r
3542             }\r
3543         }\r
3544         return true;\r
3545     },\r
3546 \r
3547     // private\r
3548     removeDropIndicators : function(n){\r
3549         if(n){\r
3550             Ext.fly(n).removeClass([\r
3551                 "x-view-drag-insert-above",\r
3552                 "x-view-drag-insert-left",\r
3553                 "x-view-drag-insert-right",\r
3554                 "x-view-drag-insert-below"]);\r
3555             this.lastInsertClass = "_noclass";\r
3556         }\r
3557     },\r
3558 \r
3559     // private\r
3560     setDroppable: function(ddGroup){\r
3561         if (!ddGroup) return;\r
3562         if (Ext.isArray(ddGroup)) {\r
3563             Ext.each(ddGroup, this.setDroppable, this);\r
3564             return;\r
3565         }\r
3566         this.addToGroup(ddGroup);\r
3567     }\r
3568 });\r
3569
3570 /* Fix for Opera, which does not seem to include the map function on Array's */
3571 if (!Array.prototype.map) {
3572     Array.prototype.map = function(fun){
3573         var len = this.length;
3574         if (typeof fun != 'function') {
3575             throw new TypeError();
3576         }
3577         var res = new Array(len);
3578         var thisp = arguments[1];
3579         for (var i = 0; i < len; i++) {
3580             if (i in this) {
3581                 res[i] = fun.call(thisp, this[i], i, this);
3582             }
3583         }
3584         return res;
3585     };
3586 }
3587
3588 Ext.ns('Ext.ux.data');
3589
3590 /**
3591  * @class Ext.ux.data.PagingMemoryProxy
3592  * @extends Ext.data.MemoryProxy
3593  * <p>Paging Memory Proxy, allows to use paging grid with in memory dataset</p>
3594  */
3595 Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, {
3596     constructor : function(data){
3597         Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this);
3598         this.data = data;
3599     },
3600     doRequest : function(action, rs, params, reader, callback, scope, options){
3601         params = params ||
3602         {};
3603         var result;
3604         try {
3605             result = reader.readRecords(this.data);
3606         } 
3607         catch (e) {
3608             this.fireEvent('loadexception', this, options, null, e);
3609             callback.call(scope, null, options, false);
3610             return;
3611         }
3612         
3613         // filtering
3614         if (params.filter !== undefined) {
3615             result.records = result.records.filter(function(el){
3616                 if (typeof(el) == 'object') {
3617                     var att = params.filterCol || 0;
3618                     return String(el.data[att]).match(params.filter) ? true : false;
3619                 }
3620                 else {
3621                     return String(el).match(params.filter) ? true : false;
3622                 }
3623             });
3624             result.totalRecords = result.records.length;
3625         }
3626         
3627         // sorting
3628         if (params.sort !== undefined) {
3629             // use integer as params.sort to specify column, since arrays are not named
3630             // params.sort=0; would also match a array without columns
3631             var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1;
3632             var fn = function(r1, r2){
3633                 return r1 < r2;
3634             };
3635             result.records.sort(function(a, b){
3636                 var v = 0;
3637                 if (typeof(a) == 'object') {
3638                     v = fn(a.data[params.sort], b.data[params.sort]) * dir;
3639                 }
3640                 else {
3641                     v = fn(a, b) * dir;
3642                 }
3643                 if (v == 0) {
3644                     v = (a.index < b.index ? -1 : 1);
3645                 }
3646                 return v;
3647             });
3648         }
3649         // paging (use undefined cause start can also be 0 (thus false))
3650         if (params.start !== undefined && params.limit !== undefined) {
3651             result.records = result.records.slice(params.start, params.start + params.limit);
3652         }
3653         callback.call(scope, result, options, true);
3654     }
3655 });
3656
3657 //backwards compat.
3658 Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
3659 Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {\r
3660     minHeight: 0,\r
3661     maxHeight:10000000,\r
3662 \r
3663     constructor: function(config){\r
3664         Ext.apply(this, config);\r
3665         this.events = {};\r
3666         Ext.ux.PanelResizer.superclass.constructor.call(this, config);\r
3667     },\r
3668 \r
3669     init : function(p){\r
3670         this.panel = p;\r
3671 \r
3672         if(this.panel.elements.indexOf('footer')==-1){\r
3673             p.elements += ',footer';\r
3674         }\r
3675         p.on('render', this.onRender, this);\r
3676     },\r
3677 \r
3678     onRender : function(p){\r
3679         this.handle = p.footer.createChild({cls:'x-panel-resize'});\r
3680 \r
3681         this.tracker = new Ext.dd.DragTracker({\r
3682             onStart: this.onDragStart.createDelegate(this),\r
3683             onDrag: this.onDrag.createDelegate(this),\r
3684             onEnd: this.onDragEnd.createDelegate(this),\r
3685             tolerance: 3,\r
3686             autoStart: 300\r
3687         });\r
3688         this.tracker.initEl(this.handle);\r
3689         p.on('beforedestroy', this.tracker.destroy, this.tracker);\r
3690     },\r
3691 \r
3692         // private\r
3693     onDragStart: function(e){\r
3694         this.dragging = true;\r
3695         this.startHeight = this.panel.el.getHeight();\r
3696         this.fireEvent('dragstart', this, e);\r
3697     },\r
3698 \r
3699         // private\r
3700     onDrag: function(e){\r
3701         this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));\r
3702         this.fireEvent('drag', this, e);\r
3703     },\r
3704 \r
3705         // private\r
3706     onDragEnd: function(e){\r
3707         this.dragging = false;\r
3708         this.fireEvent('dragend', this, e);\r
3709     }\r
3710 });\r
3711 Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {\r
3712     layout : 'column',\r
3713     autoScroll : true,\r
3714     cls : 'x-portal',\r
3715     defaultType : 'portalcolumn',\r
3716     \r
3717     initComponent : function(){\r
3718         Ext.ux.Portal.superclass.initComponent.call(this);\r
3719         this.addEvents({\r
3720             validatedrop:true,\r
3721             beforedragover:true,\r
3722             dragover:true,\r
3723             beforedrop:true,\r
3724             drop:true\r
3725         });\r
3726     },\r
3727 \r
3728     initEvents : function(){\r
3729         Ext.ux.Portal.superclass.initEvents.call(this);\r
3730         this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);\r
3731     },\r
3732     \r
3733     beforeDestroy : function() {\r
3734         if(this.dd){\r
3735             this.dd.unreg();\r
3736         }\r
3737         Ext.ux.Portal.superclass.beforeDestroy.call(this);\r
3738     }\r
3739 });\r
3740 \r
3741 Ext.reg('portal', Ext.ux.Portal);\r
3742 \r
3743 \r
3744 Ext.ux.Portal.DropZone = function(portal, cfg){\r
3745     this.portal = portal;\r
3746     Ext.dd.ScrollManager.register(portal.body);\r
3747     Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);\r
3748     portal.body.ddScrollConfig = this.ddScrollConfig;\r
3749 };\r
3750 \r
3751 Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {\r
3752     ddScrollConfig : {\r
3753         vthresh: 50,\r
3754         hthresh: -1,\r
3755         animate: true,\r
3756         increment: 200\r
3757     },\r
3758 \r
3759     createEvent : function(dd, e, data, col, c, pos){\r
3760         return {\r
3761             portal: this.portal,\r
3762             panel: data.panel,\r
3763             columnIndex: col,\r
3764             column: c,\r
3765             position: pos,\r
3766             data: data,\r
3767             source: dd,\r
3768             rawEvent: e,\r
3769             status: this.dropAllowed\r
3770         };\r
3771     },\r
3772 \r
3773     notifyOver : function(dd, e, data){\r
3774         var xy = e.getXY(), portal = this.portal, px = dd.proxy;\r
3775 \r
3776         // case column widths\r
3777         if(!this.grid){\r
3778             this.grid = this.getGrid();\r
3779         }\r
3780 \r
3781         // handle case scroll where scrollbars appear during drag\r
3782         var cw = portal.body.dom.clientWidth;\r
3783         if(!this.lastCW){\r
3784             this.lastCW = cw;\r
3785         }else if(this.lastCW != cw){\r
3786             this.lastCW = cw;\r
3787             portal.doLayout();\r
3788             this.grid = this.getGrid();\r
3789         }\r
3790 \r
3791         // determine column\r
3792         var col = 0, xs = this.grid.columnX, cmatch = false;\r
3793         for(var len = xs.length; col < len; col++){\r
3794             if(xy[0] < (xs[col].x + xs[col].w)){\r
3795                 cmatch = true;\r
3796                 break;\r
3797             }\r
3798         }\r
3799         // no match, fix last index\r
3800         if(!cmatch){\r
3801             col--;\r
3802         }\r
3803 \r
3804         // find insert position\r
3805         var p, match = false, pos = 0,\r
3806             c = portal.items.itemAt(col),\r
3807             items = c.items.items, overSelf = false;\r
3808 \r
3809         for(var len = items.length; pos < len; pos++){\r
3810             p = items[pos];\r
3811             var h = p.el.getHeight();\r
3812             if(h === 0){\r
3813                 overSelf = true;\r
3814             }\r
3815             else if((p.el.getY()+(h/2)) > xy[1]){\r
3816                 match = true;\r
3817                 break;\r
3818             }\r
3819         }\r
3820 \r
3821         pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);\r
3822         var overEvent = this.createEvent(dd, e, data, col, c, pos);\r
3823 \r
3824         if(portal.fireEvent('validatedrop', overEvent) !== false &&\r
3825            portal.fireEvent('beforedragover', overEvent) !== false){\r
3826 \r
3827             // make sure proxy width is fluid\r
3828             px.getProxy().setWidth('auto');\r
3829 \r
3830             if(p){\r
3831                 px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);\r
3832             }else{\r
3833                 px.moveProxy(c.el.dom, null);\r
3834             }\r
3835 \r
3836             this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};\r
3837             this.scrollPos = portal.body.getScroll();\r
3838 \r
3839             portal.fireEvent('dragover', overEvent);\r
3840 \r
3841             return overEvent.status;\r
3842         }else{\r
3843             return overEvent.status;\r
3844         }\r
3845 \r
3846     },\r
3847 \r
3848     notifyOut : function(){\r
3849         delete this.grid;\r
3850     },\r
3851 \r
3852     notifyDrop : function(dd, e, data){\r
3853         delete this.grid;\r
3854         if(!this.lastPos){\r
3855             return;\r
3856         }\r
3857         var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;\r
3858 \r
3859         var dropEvent = this.createEvent(dd, e, data, col, c,\r
3860             pos !== false ? pos : c.items.getCount());\r
3861 \r
3862         if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&\r
3863            this.portal.fireEvent('beforedrop', dropEvent) !== false){\r
3864 \r
3865             dd.proxy.getProxy().remove();\r
3866             dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);\r
3867             \r
3868             if(pos !== false){\r
3869                 if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){\r
3870                     pos++;\r
3871                 }\r
3872                 c.insert(pos, dd.panel);\r
3873             }else{\r
3874                 c.add(dd.panel);\r
3875             }\r
3876             \r
3877             c.doLayout();\r
3878 \r
3879             this.portal.fireEvent('drop', dropEvent);\r
3880 \r
3881             // scroll position is lost on drop, fix it\r
3882             var st = this.scrollPos.top;\r
3883             if(st){\r
3884                 var d = this.portal.body.dom;\r
3885                 setTimeout(function(){\r
3886                     d.scrollTop = st;\r
3887                 }, 10);\r
3888             }\r
3889 \r
3890         }\r
3891         delete this.lastPos;\r
3892     },\r
3893 \r
3894     // internal cache of body and column coords\r
3895     getGrid : function(){\r
3896         var box = this.portal.bwrap.getBox();\r
3897         box.columnX = [];\r
3898         this.portal.items.each(function(c){\r
3899              box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});\r
3900         });\r
3901         return box;\r
3902     },\r
3903 \r
3904     // unregister the dropzone from ScrollManager\r
3905     unreg: function() {\r
3906         //Ext.dd.ScrollManager.unregister(this.portal.body);\r
3907         Ext.ux.Portal.DropZone.superclass.unreg.call(this);\r
3908     }\r
3909 });\r
3910 Ext.ux.PortalColumn = Ext.extend(Ext.Container, {\r
3911     layout : 'anchor',\r
3912     //autoEl : 'div',//already defined by Ext.Component\r
3913     defaultType : 'portlet',\r
3914     cls : 'x-portal-column'\r
3915 });\r
3916 \r
3917 Ext.reg('portalcolumn', Ext.ux.PortalColumn);\r
3918 Ext.ux.Portlet = Ext.extend(Ext.Panel, {\r
3919     anchor : '100%',\r
3920     frame : true,\r
3921     collapsible : true,\r
3922     draggable : true,\r
3923     cls : 'x-portlet'\r
3924 });\r
3925 \r
3926 Ext.reg('portlet', Ext.ux.Portlet);\r
3927 /**
3928 * @class Ext.ux.ProgressBarPager
3929 * @extends Object 
3930 * Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text
3931
3932 * @ptype progressbarpager 
3933 * @constructor
3934 * Create a new ItemSelector
3935 * @param {Object} config Configuration options
3936 * @xtype itemselector 
3937 */
3938 Ext.ux.ProgressBarPager  = Ext.extend(Object, {
3939         /**
3940         * @cfg {Integer} progBarWidth
3941         * <p>The default progress bar width.  Default is 225.</p>
3942         */
3943         progBarWidth   : 225,
3944         /**
3945         * @cfg {String} defaultText
3946         * <p>The text to display while the store is loading.  Default is 'Loading...'</p>
3947         */
3948         defaultText    : 'Loading...',
3949         /**
3950         * @cfg {Object} defaultAnimCfg 
3951         * <p>A {@link Ext.Fx Ext.Fx} configuration object.  Default is  { duration : 1, easing : 'bounceOut' }.</p>
3952         */
3953         defaultAnimCfg : {
3954                 duration   : 1,
3955                 easing     : 'bounceOut'        
3956         },                                                                                                
3957         constructor : function(config) {
3958                 if (config) {
3959                         Ext.apply(this, config);
3960                 }
3961         },
3962         //public
3963         init : function (parent) {
3964                 
3965                 if(parent.displayInfo){
3966                         this.parent = parent;
3967                         var ind  = parent.items.indexOf(parent.displayItem);
3968                         parent.remove(parent.displayItem, true);
3969                         this.progressBar = new Ext.ProgressBar({
3970                                 text    : this.defaultText,
3971                                 width   : this.progBarWidth,
3972                                 animate :  this.defaultAnimCfg
3973                         });                                     
3974                    
3975                         parent.displayItem = this.progressBar;
3976                         
3977                         parent.add(parent.displayItem); 
3978                         parent.doLayout();
3979                         Ext.apply(parent, this.parentOverrides);                
3980                         
3981                         this.progressBar.on('render', function(pb) {
3982                                 pb.el.applyStyles('cursor:pointer');
3983
3984                                 pb.el.on('click', this.handleProgressBarClick, this);
3985                         }, this);
3986                         
3987                 
3988                         // Remove the click handler from the 
3989                         this.progressBar.on({
3990                                 scope         : this,
3991                                 beforeDestroy : function() {
3992                                         this.progressBar.el.un('click', this.handleProgressBarClick, this);     
3993                                 }
3994                         });     
3995                                                 
3996                 }
3997                   
3998         },
3999         // private
4000         // This method handles the click for the progress bar
4001         handleProgressBarClick : function(e){
4002                 var parent = this.parent;
4003                 var displayItem = parent.displayItem;
4004                 
4005                 var box = this.progressBar.getBox();
4006                 var xy = e.getXY();
4007                 var position = xy[0]-box.x;
4008                 var pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize);
4009                 
4010                 var newpage = Math.ceil(position/(displayItem.width/pages));
4011                 parent.changePage(newpage);
4012         },
4013         
4014         // private, overriddes
4015         parentOverrides  : {
4016                 // private
4017                 // This method updates the information via the progress bar.
4018                 updateInfo : function(){
4019                         if(this.displayItem){
4020                                 var count   = this.store.getCount();
4021                                 var pgData  = this.getPageData();
4022                                 var pageNum = this.readPage(pgData);
4023                                 
4024                                 var msg    = count == 0 ?
4025                                         this.emptyMsg :
4026                                         String.format(
4027                                                 this.displayMsg,
4028                                                 this.cursor+1, this.cursor+count, this.store.getTotalCount()
4029                                         );
4030                                         
4031                                 pageNum = pgData.activePage; ;  
4032                                 
4033                                 var pct = pageNum / pgData.pages;       
4034                                 
4035                                 this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig);
4036                         }
4037                 }
4038         }
4039 });
4040 Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
4041
4042 Ext.ns('Ext.ux.grid');
4043
4044 /**
4045  * @class Ext.ux.grid.RowEditor
4046  * @extends Ext.Panel 
4047  * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
4048  * A validation mode may be enabled which uses AnchorTips to notify the user of all
4049  * validation errors at once.
4050  * 
4051  * @ptype roweditor
4052  */
4053 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
4054     floating: true,
4055     shadow: false,
4056     layout: 'hbox',
4057     cls: 'x-small-editor',
4058     buttonAlign: 'center',
4059     baseCls: 'x-row-editor',
4060     elements: 'header,footer,body',
4061     frameWidth: 5,
4062     buttonPad: 3,
4063     clicksToEdit: 'auto',
4064     monitorValid: true,
4065     focusDelay: 250,
4066     errorSummary: true,
4067
4068     defaults: {
4069         normalWidth: true
4070     },
4071
4072     initComponent: function(){
4073         Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
4074         this.addEvents(
4075             /**
4076              * @event beforeedit
4077              * Fired before the row editor is activated.
4078              * If the listener returns <tt>false</tt> the editor will not be activated.
4079              * @param {Ext.ux.grid.RowEditor} roweditor This object
4080              * @param {Number} rowIndex The rowIndex of the row just edited
4081              */
4082             'beforeedit',
4083             /**
4084              * @event validateedit
4085              * Fired after a row is edited and passes validation.
4086              * If the listener returns <tt>false</tt> changes to the record will not be set.
4087              * @param {Ext.ux.grid.RowEditor} roweditor This object
4088              * @param {Object} changes Object with changes made to the record.
4089              * @param {Ext.data.Record} r The Record that was edited.
4090              * @param {Number} rowIndex The rowIndex of the row just edited
4091              */
4092             'validateedit',
4093             /**
4094              * @event afteredit
4095              * Fired after a row is edited and passes validation.  This event is fired
4096              * after the store's update event is fired with this edit.
4097              * @param {Ext.ux.grid.RowEditor} roweditor This object
4098              * @param {Object} changes Object with changes made to the record.
4099              * @param {Ext.data.Record} r The Record that was edited.
4100              * @param {Number} rowIndex The rowIndex of the row just edited
4101              */
4102             'afteredit'
4103         );
4104     },
4105
4106     init: function(grid){
4107         this.grid = grid;
4108         this.ownerCt = grid;
4109         if(this.clicksToEdit === 2){
4110             grid.on('rowdblclick', this.onRowDblClick, this);
4111         }else{
4112             grid.on('rowclick', this.onRowClick, this);
4113             if(Ext.isIE){
4114                 grid.on('rowdblclick', this.onRowDblClick, this);
4115             }
4116         }
4117
4118         // stopEditing without saving when a record is removed from Store.
4119         grid.getStore().on('remove', function() {
4120             this.stopEditing(false);
4121         },this);
4122
4123         grid.on({
4124             scope: this,
4125             keydown: this.onGridKey,
4126             columnresize: this.verifyLayout,
4127             columnmove: this.refreshFields,
4128             reconfigure: this.refreshFields,
4129             destroy : this.destroy,
4130             bodyscroll: {
4131                 buffer: 250,
4132                 fn: this.positionButtons
4133             }
4134         });
4135         grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
4136         grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
4137     },
4138
4139     refreshFields: function(){
4140         this.initFields();
4141         this.verifyLayout();
4142     },
4143
4144     isDirty: function(){
4145         var dirty;
4146         this.items.each(function(f){
4147             if(String(this.values[f.id]) !== String(f.getValue())){
4148                 dirty = true;
4149                 return false;
4150             }
4151         }, this);
4152         return dirty;
4153     },
4154
4155     startEditing: function(rowIndex, doFocus){
4156         if(this.editing && this.isDirty()){
4157             this.showTooltip('You need to commit or cancel your changes');
4158             return;
4159         }
4160         this.editing = true;
4161         if(typeof rowIndex == 'object'){
4162             rowIndex = this.grid.getStore().indexOf(rowIndex);
4163         }
4164         if(this.fireEvent('beforeedit', this, rowIndex) !== false){
4165             var g = this.grid, view = g.getView();
4166             var row = view.getRow(rowIndex);
4167             var record = g.store.getAt(rowIndex);
4168             this.record = record;
4169             this.rowIndex = rowIndex;
4170             this.values = {};
4171             if(!this.rendered){
4172                 this.render(view.getEditorParent());
4173             }
4174             var w = Ext.fly(row).getWidth();
4175             this.setSize(w);
4176             if(!this.initialized){
4177                 this.initFields();
4178             }
4179             var cm = g.getColumnModel(), fields = this.items.items, f, val;
4180             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
4181                 val = this.preEditValue(record, cm.getDataIndex(i));
4182                 f = fields[i];
4183                 f.setValue(val);
4184                 this.values[f.id] = val || '';
4185             }
4186             this.verifyLayout(true);
4187             if(!this.isVisible()){
4188                 this.setPagePosition(Ext.fly(row).getXY());
4189             } else{
4190                 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
4191             }
4192             if(!this.isVisible()){
4193                 this.show().doLayout();
4194             }
4195             if(doFocus !== false){
4196                 this.doFocus.defer(this.focusDelay, this);
4197             }
4198         }
4199     },
4200
4201     stopEditing : function(saveChanges){
4202         this.editing = false;
4203         if(!this.isVisible()){
4204             return;
4205         }
4206         if(saveChanges === false || !this.isValid()){
4207             this.hide();
4208             return;
4209         }
4210         var changes = {}, r = this.record, hasChange = false;
4211         var cm = this.grid.colModel, fields = this.items.items;
4212         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
4213             if(!cm.isHidden(i)){
4214                 var dindex = cm.getDataIndex(i);
4215                 if(!Ext.isEmpty(dindex)){
4216                     var oldValue = r.data[dindex];
4217                     var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
4218                     if(String(oldValue) !== String(value)){
4219                         changes[dindex] = value;
4220                         hasChange = true;
4221                     }
4222                 }
4223             }
4224         }
4225         if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
4226             r.beginEdit();
4227             for(var k in changes){
4228                 if(changes.hasOwnProperty(k)){
4229                     r.set(k, changes[k]);
4230                 }
4231             }
4232             r.endEdit();
4233             this.fireEvent('afteredit', this, changes, r, this.rowIndex);
4234         }
4235         this.hide();
4236     },
4237
4238     verifyLayout: function(force){
4239         if(this.el && (this.isVisible() || force === true)){
4240             var row = this.grid.getView().getRow(this.rowIndex);
4241             this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + (Ext.isBorderBox ? 9 : 0) : undefined);
4242             var cm = this.grid.colModel, fields = this.items.items;
4243             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
4244                 if(!cm.isHidden(i)){
4245                     var adjust = 0;
4246                     if(i === 0){
4247                         adjust += 0; // outer padding
4248                     }
4249                     if(i === (len - 1)){
4250                         adjust += 3; // outer padding
4251                     } else{
4252                         adjust += 1;
4253                     }
4254                     fields[i].show();
4255                     fields[i].setWidth(cm.getColumnWidth(i) - adjust);
4256                 } else{
4257                     fields[i].hide();
4258                 }
4259             }
4260             this.doLayout();
4261             this.positionButtons();
4262         }
4263     },
4264
4265     slideHide : function(){
4266         this.hide();
4267     },
4268
4269     initFields: function(){
4270         var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
4271         this.removeAll(false);
4272         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
4273             var c = cm.getColumnAt(i);
4274             var ed = c.getEditor();
4275             if(!ed){
4276                 ed = c.displayEditor || new Ext.form.DisplayField();
4277             }
4278             if(i == 0){
4279                 ed.margins = pm('0 1 2 1');
4280             } else if(i == len - 1){
4281                 ed.margins = pm('0 0 2 1');
4282             } else{
4283                 ed.margins = pm('0 1 2');
4284             }
4285             ed.setWidth(cm.getColumnWidth(i));
4286             ed.column = c;
4287             if(ed.ownerCt !== this){
4288                 ed.on('focus', this.ensureVisible, this);
4289                 ed.on('specialkey', this.onKey, this);
4290             }
4291             this.insert(i, ed);
4292         }
4293         this.initialized = true;
4294     },
4295
4296     onKey: function(f, e){
4297         if(e.getKey() === e.ENTER){
4298             this.stopEditing(true);
4299             e.stopPropagation();
4300         }
4301     },
4302
4303     onGridKey: function(e){
4304         if(e.getKey() === e.ENTER && !this.isVisible()){
4305             var r = this.grid.getSelectionModel().getSelected();
4306             if(r){
4307                 var index = this.grid.store.indexOf(r);
4308                 this.startEditing(index);
4309                 e.stopPropagation();
4310             }
4311         }
4312     },
4313
4314     ensureVisible: function(editor){
4315         if(this.isVisible()){
4316              this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
4317         }
4318     },
4319
4320     onRowClick: function(g, rowIndex, e){
4321         if(this.clicksToEdit == 'auto'){
4322             var li = this.lastClickIndex;
4323             this.lastClickIndex = rowIndex;
4324             if(li != rowIndex && !this.isVisible()){
4325                 return;
4326             }
4327         }
4328         this.startEditing(rowIndex, false);
4329         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
4330     },
4331
4332     onRowDblClick: function(g, rowIndex, e){
4333         this.startEditing(rowIndex, false);
4334         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
4335     },
4336
4337     onRender: function(){
4338         Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
4339         this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
4340         this.btns = new Ext.Panel({
4341             baseCls: 'x-plain',
4342             cls: 'x-btns',
4343             elements:'body',
4344             layout: 'table',
4345             width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
4346             items: [{
4347                 ref: 'saveBtn',
4348                 itemId: 'saveBtn',
4349                 xtype: 'button',
4350                 text: this.saveText || 'Save',
4351                 width: this.minButtonWidth,
4352                 handler: this.stopEditing.createDelegate(this, [true])
4353             }, {
4354                 xtype: 'button',
4355                 text: this.cancelText || 'Cancel',
4356                 width: this.minButtonWidth,
4357                 handler: this.stopEditing.createDelegate(this, [false])
4358             }]
4359         });
4360         this.btns.render(this.bwrap);
4361     },
4362
4363     afterRender: function(){
4364         Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
4365         this.positionButtons();
4366         if(this.monitorValid){
4367             this.startMonitoring();
4368         }
4369     },
4370
4371     onShow: function(){
4372         if(this.monitorValid){
4373             this.startMonitoring();
4374         }
4375         Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
4376     },
4377
4378     onHide: function(){
4379         Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
4380         this.stopMonitoring();
4381         this.grid.getView().focusRow(this.rowIndex);
4382     },
4383
4384     positionButtons: function(){
4385         if(this.btns){
4386             var h = this.el.dom.clientHeight;
4387             var view = this.grid.getView();
4388             var scroll = view.scroller.dom.scrollLeft;
4389             var width =  view.mainBody.getWidth();
4390             var bw = this.btns.getWidth();
4391             this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
4392         }
4393     },
4394
4395     // private
4396     preEditValue : function(r, field){
4397         var value = r.data[field];
4398         return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
4399     },
4400
4401     // private
4402     postEditValue : function(value, originalValue, r, field){
4403         return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
4404     },
4405
4406     doFocus: function(pt){
4407         if(this.isVisible()){
4408             var index = 0;
4409             if(pt){
4410                 index = this.getTargetColumnIndex(pt);
4411             }
4412             var cm = this.grid.getColumnModel();
4413             for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
4414                 var c = cm.getColumnAt(i);
4415                 if(!c.hidden && c.getEditor()){
4416                     c.getEditor().focus();
4417                     break;
4418                 }
4419             }
4420         }
4421     },
4422
4423     getTargetColumnIndex: function(pt){
4424         var grid = this.grid, v = grid.view;
4425         var x = pt.left;
4426         var cms = grid.colModel.config;
4427         var i = 0, match = false;
4428         for(var len = cms.length, c; c = cms[i]; i++){
4429             if(!c.hidden){
4430                 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
4431                     match = i;
4432                     break;
4433                 }
4434             }
4435         }
4436         return match;
4437     },
4438
4439     startMonitoring : function(){
4440         if(!this.bound && this.monitorValid){
4441             this.bound = true;
4442             Ext.TaskMgr.start({
4443                 run : this.bindHandler,
4444                 interval : this.monitorPoll || 200,
4445                 scope: this
4446             });
4447         }
4448     },
4449
4450     stopMonitoring : function(){
4451         this.bound = false;
4452         if(this.tooltip){
4453             this.tooltip.hide();
4454         }
4455     },
4456
4457     isValid: function(){
4458         var valid = true;
4459         this.items.each(function(f){
4460             if(!f.isValid(true)){
4461                 valid = false;
4462                 return false;
4463             }
4464         });
4465         return valid;
4466     },
4467
4468     // private
4469     bindHandler : function(){
4470         if(!this.bound){
4471             return false; // stops binding
4472         }
4473         var valid = this.isValid();
4474         if(!valid && this.errorSummary){
4475             this.showTooltip(this.getErrorText().join(''));
4476         }
4477         this.btns.saveBtn.setDisabled(!valid);
4478         this.fireEvent('validation', this, valid);
4479     },
4480
4481     showTooltip: function(msg){
4482         var t = this.tooltip;
4483         if(!t){
4484             t = this.tooltip = new Ext.ToolTip({
4485                 maxWidth: 600,
4486                 cls: 'errorTip',
4487                 width: 300,
4488                 title: 'Errors',
4489                 autoHide: false,
4490                 anchor: 'left',
4491                 anchorToTarget: true,
4492                 mouseOffset: [40,0]
4493             });
4494         }
4495         t.initTarget(this.items.last().getEl());
4496         if(!t.rendered){
4497             t.show();
4498             t.hide();
4499         }
4500         t.body.update(msg);
4501         t.doAutoWidth();
4502         t.show();
4503     },
4504
4505     getErrorText: function(){
4506         var data = ['<ul>'];
4507         this.items.each(function(f){
4508             if(!f.isValid(true)){
4509                 data.push('<li>', f.activeError, '</li>');
4510             }
4511         });
4512         data.push('</ul>');
4513         return data;
4514     }
4515 });
4516 Ext.preg('roweditor', Ext.ux.grid.RowEditor);
4517
4518 Ext.override(Ext.form.Field, {
4519     markInvalid : function(msg){
4520         if(!this.rendered || this.preventMark){ // not rendered
4521             return;
4522         }
4523         msg = msg || this.invalidText;
4524
4525         var mt = this.getMessageHandler();
4526         if(mt){
4527             mt.mark(this, msg);
4528         }else if(this.msgTarget){
4529             this.el.addClass(this.invalidClass);
4530             var t = Ext.getDom(this.msgTarget);
4531             if(t){
4532                 t.innerHTML = msg;
4533                 t.style.display = this.msgDisplay;
4534             }
4535         }
4536         this.activeError = msg;
4537         this.fireEvent('invalid', this, msg);
4538     }
4539 });
4540
4541 Ext.override(Ext.ToolTip, {
4542     doAutoWidth : function(){
4543         var bw = this.body.getTextWidth();
4544         if(this.title){
4545             bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
4546         }
4547         bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
4548         this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
4549
4550         // IE7 repaint bug on initial show
4551         if(Ext.isIE7 && !this.repainted){
4552             this.el.repaint();
4553             this.repainted = true;
4554         }
4555     }
4556 });
4557 Ext.ns('Ext.ux.grid');\r
4558 \r
4559 /**\r
4560  * @class Ext.ux.grid.RowExpander\r
4561  * @extends Ext.util.Observable\r
4562  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables\r
4563  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react\r
4564  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.\r
4565  *\r
4566  * @ptype rowexpander\r
4567  */\r
4568 Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {\r
4569     /**\r
4570      * @cfg {Boolean} expandOnEnter\r
4571      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter\r
4572      * key is pressed (defaults to <tt>true</tt>).\r
4573      */\r
4574     expandOnEnter : true,\r
4575     /**\r
4576      * @cfg {Boolean} expandOnDblClick\r
4577      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked\r
4578      * (defaults to <tt>true</tt>).\r
4579      */\r
4580     expandOnDblClick : true,\r
4581 \r
4582     header : '',\r
4583     width : 20,\r
4584     sortable : false,\r
4585     fixed : true,\r
4586     menuDisabled : true,\r
4587     dataIndex : '',\r
4588     id : 'expander',\r
4589     lazyRender : true,\r
4590     enableCaching : true,\r
4591 \r
4592     constructor: function(config){\r
4593         Ext.apply(this, config);\r
4594 \r
4595         this.addEvents({\r
4596             /**\r
4597              * @event beforeexpand\r
4598              * Fires before the row expands. Have the listener return false to prevent the row from expanding.\r
4599              * @param {Object} this RowExpander object.\r
4600              * @param {Object} Ext.data.Record Record for the selected row.\r
4601              * @param {Object} body body element for the secondary row.\r
4602              * @param {Number} rowIndex The current row index.\r
4603              */\r
4604             beforeexpand: true,\r
4605             /**\r
4606              * @event expand\r
4607              * Fires after the row expands.\r
4608              * @param {Object} this RowExpander object.\r
4609              * @param {Object} Ext.data.Record Record for the selected row.\r
4610              * @param {Object} body body element for the secondary row.\r
4611              * @param {Number} rowIndex The current row index.\r
4612              */\r
4613             expand: true,\r
4614             /**\r
4615              * @event beforecollapse\r
4616              * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.\r
4617              * @param {Object} this RowExpander object.\r
4618              * @param {Object} Ext.data.Record Record for the selected row.\r
4619              * @param {Object} body body element for the secondary row.\r
4620              * @param {Number} rowIndex The current row index.\r
4621              */\r
4622             beforecollapse: true,\r
4623             /**\r
4624              * @event collapse\r
4625              * Fires after the row collapses.\r
4626              * @param {Object} this RowExpander object.\r
4627              * @param {Object} Ext.data.Record Record for the selected row.\r
4628              * @param {Object} body body element for the secondary row.\r
4629              * @param {Number} rowIndex The current row index.\r
4630              */\r
4631             collapse: true\r
4632         });\r
4633 \r
4634         Ext.ux.grid.RowExpander.superclass.constructor.call(this);\r
4635 \r
4636         if(this.tpl){\r
4637             if(typeof this.tpl == 'string'){\r
4638                 this.tpl = new Ext.Template(this.tpl);\r
4639             }\r
4640             this.tpl.compile();\r
4641         }\r
4642 \r
4643         this.state = {};\r
4644         this.bodyContent = {};\r
4645     },\r
4646 \r
4647     getRowClass : function(record, rowIndex, p, ds){\r
4648         p.cols = p.cols-1;\r
4649         var content = this.bodyContent[record.id];\r
4650         if(!content && !this.lazyRender){\r
4651             content = this.getBodyContent(record, rowIndex);\r
4652         }\r
4653         if(content){\r
4654             p.body = content;\r
4655         }\r
4656         return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';\r
4657     },\r
4658 \r
4659     init : function(grid){\r
4660         this.grid = grid;\r
4661 \r
4662         var view = grid.getView();\r
4663         view.getRowClass = this.getRowClass.createDelegate(this);\r
4664 \r
4665         view.enableRowBody = true;\r
4666 \r
4667 \r
4668         grid.on('render', this.onRender, this);\r
4669         grid.on('destroy', this.onDestroy, this);\r
4670     },\r
4671 \r
4672     // @private\r
4673     onRender: function() {\r
4674         var grid = this.grid;\r
4675         var mainBody = grid.getView().mainBody;\r
4676         mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});\r
4677         if (this.expandOnEnter) {\r
4678             this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {\r
4679                 'enter' : this.onEnter,\r
4680                 scope: this\r
4681             });\r
4682         }\r
4683         if (this.expandOnDblClick) {\r
4684             grid.on('rowdblclick', this.onRowDblClick, this);\r
4685         }\r
4686     },\r
4687     \r
4688     // @private    \r
4689     onDestroy: function() {\r
4690         this.keyNav.disable();\r
4691         delete this.keyNav;\r
4692         var mainBody = this.grid.getView().mainBody;\r
4693         mainBody.un('mousedown', this.onMouseDown, this);\r
4694     },\r
4695     // @private\r
4696     onRowDblClick: function(grid, rowIdx, e) {\r
4697         this.toggleRow(rowIdx);\r
4698     },\r
4699 \r
4700     onEnter: function(e) {\r
4701         var g = this.grid;\r
4702         var sm = g.getSelectionModel();\r
4703         var sels = sm.getSelections();\r
4704         for (var i = 0, len = sels.length; i < len; i++) {\r
4705             var rowIdx = g.getStore().indexOf(sels[i]);\r
4706             this.toggleRow(rowIdx);\r
4707         }\r
4708     },\r
4709 \r
4710     getBodyContent : function(record, index){\r
4711         if(!this.enableCaching){\r
4712             return this.tpl.apply(record.data);\r
4713         }\r
4714         var content = this.bodyContent[record.id];\r
4715         if(!content){\r
4716             content = this.tpl.apply(record.data);\r
4717             this.bodyContent[record.id] = content;\r
4718         }\r
4719         return content;\r
4720     },\r
4721 \r
4722     onMouseDown : function(e, t){\r
4723         e.stopEvent();\r
4724         var row = e.getTarget('.x-grid3-row');\r
4725         this.toggleRow(row);\r
4726     },\r
4727 \r
4728     renderer : function(v, p, record){\r
4729         p.cellAttr = 'rowspan="2"';\r
4730         return '<div class="x-grid3-row-expander">&#160;</div>';\r
4731     },\r
4732 \r
4733     beforeExpand : function(record, body, rowIndex){\r
4734         if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){\r
4735             if(this.tpl && this.lazyRender){\r
4736                 body.innerHTML = this.getBodyContent(record, rowIndex);\r
4737             }\r
4738             return true;\r
4739         }else{\r
4740             return false;\r
4741         }\r
4742     },\r
4743 \r
4744     toggleRow : function(row){\r
4745         if(typeof row == 'number'){\r
4746             row = this.grid.view.getRow(row);\r
4747         }\r
4748         this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);\r
4749     },\r
4750 \r
4751     expandRow : function(row){\r
4752         if(typeof row == 'number'){\r
4753             row = this.grid.view.getRow(row);\r
4754         }\r
4755         var record = this.grid.store.getAt(row.rowIndex);\r
4756         var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);\r
4757         if(this.beforeExpand(record, body, row.rowIndex)){\r
4758             this.state[record.id] = true;\r
4759             Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');\r
4760             this.fireEvent('expand', this, record, body, row.rowIndex);\r
4761         }\r
4762     },\r
4763 \r
4764     collapseRow : function(row){\r
4765         if(typeof row == 'number'){\r
4766             row = this.grid.view.getRow(row);\r
4767         }\r
4768         var record = this.grid.store.getAt(row.rowIndex);\r
4769         var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);\r
4770         if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){\r
4771             this.state[record.id] = false;\r
4772             Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');\r
4773             this.fireEvent('collapse', this, record, body, row.rowIndex);\r
4774         }\r
4775     }\r
4776 });\r
4777 \r
4778 Ext.preg('rowexpander', Ext.ux.grid.RowExpander);\r
4779 \r
4780 //backwards compat\r
4781 Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not
4782 // exist by default in Ext, so we have to add the namespace first:
4783 Ext.ns('Ext.ux.layout');
4784
4785 /**
4786  * @class Ext.ux.layout.RowLayout
4787  * @extends Ext.layout.ContainerLayout
4788  * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
4789  * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
4790  * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
4791  * and should generally not need to be created directly via the new keyword.</p>
4792  * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
4793  * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
4794  * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
4795  * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
4796  * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
4797  * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and
4798  * less than 1 (e.g., .25).</p>
4799  * <p>The basic rules for specifying row heights are pretty simple.  The logic makes two passes through the
4800  * set of contained panels.  During the first layout pass, all panels that either have a fixed height or none
4801  * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
4802  * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
4803  * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
4804  * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
4805  * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your
4806  * layout may not render as expected.  Example usage:</p>
4807  * <pre><code>
4808 // All rows are percentages -- they must add up to 1
4809 var p = new Ext.Panel({
4810     title: 'Row Layout - Percentage Only',
4811     layout:'ux.row',
4812     items: [{
4813         title: 'Row 1',
4814         rowHeight: .25
4815     },{
4816         title: 'Row 2',
4817         rowHeight: .6
4818     },{
4819         title: 'Row 3',
4820         rowHeight: .15
4821     }]
4822 });
4823
4824 // Mix of height and rowHeight -- all rowHeight values must add
4825 // up to 1. The first row will take up exactly 120px, and the last two
4826 // rows will fill the remaining container height.
4827 var p = new Ext.Panel({
4828     title: 'Row Layout - Mixed',
4829     layout:'ux.row',
4830     items: [{
4831         title: 'Row 1',
4832         height: 120,
4833         // standard panel widths are still supported too:
4834         width: '50%' // or 200
4835     },{
4836         title: 'Row 2',
4837         rowHeight: .8,
4838         width: 300
4839     },{
4840         title: 'Row 3',
4841         rowHeight: .2
4842     }]
4843 });
4844 </code></pre>
4845  */
4846 Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
4847     // private
4848     monitorResize:true,
4849
4850     // private
4851     isValidParent : function(c, target){
4852         return c.getEl().dom.parentNode == this.innerCt.dom;
4853     },
4854
4855     // private
4856     onLayout : function(ct, target){
4857         var rs = ct.items.items, len = rs.length, r, i;
4858
4859         if(!this.innerCt){
4860             target.addClass('ux-row-layout-ct');
4861             this.innerCt = target.createChild({cls:'x-row-inner'});
4862         }
4863         this.renderAll(ct, this.innerCt);
4864
4865         var size = target.getViewSize();
4866
4867         if(size.width < 1 && size.height < 1){ // display none?
4868             return;
4869         }
4870
4871         var h = size.height - target.getPadding('tb'),
4872             ph = h;
4873
4874         this.innerCt.setSize({height:h});
4875
4876         // some rows can be percentages while others are fixed
4877         // so we need to make 2 passes
4878
4879         for(i = 0; i < len; i++){
4880             r = rs[i];
4881             if(!r.rowHeight){
4882                 ph -= (r.getSize().height + r.getEl().getMargins('tb'));
4883             }
4884         }
4885
4886         ph = ph < 0 ? 0 : ph;
4887
4888         for(i = 0; i < len; i++){
4889             r = rs[i];
4890             if(r.rowHeight){
4891                 r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
4892             }
4893         }
4894     }
4895
4896     /**
4897      * @property activeItem
4898      * @hide
4899      */
4900 });
4901
4902 Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
4903 Ext.ns('Ext.ux.form');\r
4904 \r
4905 Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {\r
4906     initComponent : function(){\r
4907         Ext.ux.form.SearchField.superclass.initComponent.call(this);\r
4908         this.on('specialkey', function(f, e){\r
4909             if(e.getKey() == e.ENTER){\r
4910                 this.onTrigger2Click();\r
4911             }\r
4912         }, this);\r
4913     },\r
4914 \r
4915     validationEvent:false,\r
4916     validateOnBlur:false,\r
4917     trigger1Class:'x-form-clear-trigger',\r
4918     trigger2Class:'x-form-search-trigger',\r
4919     hideTrigger1:true,\r
4920     width:180,\r
4921     hasSearch : false,\r
4922     paramName : 'query',\r
4923 \r
4924     onTrigger1Click : function(){\r
4925         if(this.hasSearch){\r
4926             this.el.dom.value = '';\r
4927             var o = {start: 0};\r
4928             this.store.baseParams = this.store.baseParams || {};\r
4929             this.store.baseParams[this.paramName] = '';\r
4930             this.store.reload({params:o});\r
4931             this.triggers[0].hide();\r
4932             this.hasSearch = false;\r
4933         }\r
4934     },\r
4935 \r
4936     onTrigger2Click : function(){\r
4937         var v = this.getRawValue();\r
4938         if(v.length < 1){\r
4939             this.onTrigger1Click();\r
4940             return;\r
4941         }\r
4942         var o = {start: 0};\r
4943         this.store.baseParams = this.store.baseParams || {};\r
4944         this.store.baseParams[this.paramName] = v;\r
4945         this.store.reload({params:o});\r
4946         this.hasSearch = true;\r
4947         this.triggers[0].show();\r
4948     }\r
4949 });Ext.ns('Ext.ux.form');\r
4950 \r
4951 /**\r
4952  * @class Ext.ux.form.SelectBox\r
4953  * @extends Ext.form.ComboBox\r
4954  * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging\r
4955  * through the list, with item selection occurring when the mouse button is released.\r
4956  * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}\r
4957  * on inner elements.  Re-enabling editable after calling this will NOT work.</p>\r
4958  * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392\r
4959  * @history 2007-07-08 jvs\r
4960  * Slight mods for Ext 2.0\r
4961  * @xtype selectbox\r
4962  */\r
4963 Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {\r
4964         constructor: function(config){\r
4965                 this.searchResetDelay = 1000;\r
4966                 config = config || {};\r
4967                 config = Ext.apply(config || {}, {\r
4968                         editable: false,\r
4969                         forceSelection: true,\r
4970                         rowHeight: false,\r
4971                         lastSearchTerm: false,\r
4972                         triggerAction: 'all',\r
4973                         mode: 'local'\r
4974                 });\r
4975 \r
4976                 Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);\r
4977 \r
4978                 this.lastSelectedIndex = this.selectedIndex || 0;\r
4979         },\r
4980 \r
4981         initEvents : function(){\r
4982                 Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);\r
4983                 // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE\r
4984                 this.el.on('keydown', this.keySearch, this, true);\r
4985                 this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);\r
4986         },\r
4987 \r
4988         keySearch : function(e, target, options) {\r
4989                 var raw = e.getKey();\r
4990                 var key = String.fromCharCode(raw);\r
4991                 var startIndex = 0;\r
4992 \r
4993                 if( !this.store.getCount() ) {\r
4994                         return;\r
4995                 }\r
4996 \r
4997                 switch(raw) {\r
4998                         case Ext.EventObject.HOME:\r
4999                                 e.stopEvent();\r
5000                                 this.selectFirst();\r
5001                                 return;\r
5002 \r
5003                         case Ext.EventObject.END:\r
5004                                 e.stopEvent();\r
5005                                 this.selectLast();\r
5006                                 return;\r
5007 \r
5008                         case Ext.EventObject.PAGEDOWN:\r
5009                                 this.selectNextPage();\r
5010                                 e.stopEvent();\r
5011                                 return;\r
5012 \r
5013                         case Ext.EventObject.PAGEUP:\r
5014                                 this.selectPrevPage();\r
5015                                 e.stopEvent();\r
5016                                 return;\r
5017                 }\r
5018 \r
5019                 // skip special keys other than the shift key\r
5020                 if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {\r
5021                         return;\r
5022                 }\r
5023                 if( this.lastSearchTerm == key ) {\r
5024                         startIndex = this.lastSelectedIndex;\r
5025                 }\r
5026                 this.search(this.displayField, key, startIndex);\r
5027                 this.cshTask.delay(this.searchResetDelay);\r
5028         },\r
5029 \r
5030         onRender : function(ct, position) {\r
5031                 this.store.on('load', this.calcRowsPerPage, this);\r
5032                 Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);\r
5033                 if( this.mode == 'local' ) {\r
5034                         this.calcRowsPerPage();\r
5035                 }\r
5036         },\r
5037 \r
5038         onSelect : function(record, index, skipCollapse){\r
5039                 if(this.fireEvent('beforeselect', this, record, index) !== false){\r
5040                         this.setValue(record.data[this.valueField || this.displayField]);\r
5041                         if( !skipCollapse ) {\r
5042                                 this.collapse();\r
5043                         }\r
5044                         this.lastSelectedIndex = index + 1;\r
5045                         this.fireEvent('select', this, record, index);\r
5046                 }\r
5047         },\r
5048 \r
5049         render : function(ct) {\r
5050                 Ext.ux.form.SelectBox.superclass.render.apply(this, arguments);\r
5051                 if( Ext.isSafari ) {\r
5052                         this.el.swallowEvent('mousedown', true);\r
5053                 }\r
5054                 this.el.unselectable();\r
5055                 this.innerList.unselectable();\r
5056                 this.trigger.unselectable();\r
5057                 this.innerList.on('mouseup', function(e, target, options) {\r
5058                         if( target.id && target.id == this.innerList.id ) {\r
5059                                 return;\r
5060                         }\r
5061                         this.onViewClick();\r
5062                 }, this);\r
5063 \r
5064                 this.innerList.on('mouseover', function(e, target, options) {\r
5065                         if( target.id && target.id == this.innerList.id ) {\r
5066                                 return;\r
5067                         }\r
5068                         this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;\r
5069                         this.cshTask.delay(this.searchResetDelay);\r
5070                 }, this);\r
5071 \r
5072                 this.trigger.un('click', this.onTriggerClick, this);\r
5073                 this.trigger.on('mousedown', function(e, target, options) {\r
5074                         e.preventDefault();\r
5075                         this.onTriggerClick();\r
5076                 }, this);\r
5077 \r
5078                 this.on('collapse', function(e, target, options) {\r
5079                         Ext.getDoc().un('mouseup', this.collapseIf, this);\r
5080                 }, this, true);\r
5081 \r
5082                 this.on('expand', function(e, target, options) {\r
5083                         Ext.getDoc().on('mouseup', this.collapseIf, this);\r
5084                 }, this, true);\r
5085         },\r
5086 \r
5087         clearSearchHistory : function() {\r
5088                 this.lastSelectedIndex = 0;\r
5089                 this.lastSearchTerm = false;\r
5090         },\r
5091 \r
5092         selectFirst : function() {\r
5093                 this.focusAndSelect(this.store.data.first());\r
5094         },\r
5095 \r
5096         selectLast : function() {\r
5097                 this.focusAndSelect(this.store.data.last());\r
5098         },\r
5099 \r
5100         selectPrevPage : function() {\r
5101                 if( !this.rowHeight ) {\r
5102                         return;\r
5103                 }\r
5104                 var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);\r
5105                 this.focusAndSelect(this.store.getAt(index));\r
5106         },\r
5107 \r
5108         selectNextPage : function() {\r
5109                 if( !this.rowHeight ) {\r
5110                         return;\r
5111                 }\r
5112                 var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);\r
5113                 this.focusAndSelect(this.store.getAt(index));\r
5114         },\r
5115 \r
5116         search : function(field, value, startIndex) {\r
5117                 field = field || this.displayField;\r
5118                 this.lastSearchTerm = value;\r
5119                 var index = this.store.find.apply(this.store, arguments);\r
5120                 if( index !== -1 ) {\r
5121                         this.focusAndSelect(index);\r
5122                 }\r
5123         },\r
5124 \r
5125         focusAndSelect : function(record) {\r
5126                 var index = typeof record === 'number' ? record : this.store.indexOf(record);\r
5127                 this.select(index, this.isExpanded());\r
5128                 this.onSelect(this.store.getAt(record), index, this.isExpanded());\r
5129         },\r
5130 \r
5131         calcRowsPerPage : function() {\r
5132                 if( this.store.getCount() ) {\r
5133                         this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();\r
5134                         this.rowsPerPage = this.maxHeight / this.rowHeight;\r
5135                 } else {\r
5136                         this.rowHeight = false;\r
5137                 }\r
5138         }\r
5139 \r
5140 });\r
5141 \r
5142 Ext.reg('selectbox', Ext.ux.form.SelectBox);\r
5143 \r
5144 //backwards compat\r
5145 Ext.ux.SelectBox = Ext.ux.form.SelectBox;\r
5146 /**\r
5147  * @class Ext.ux.SliderTip\r
5148  * @extends Ext.Tip\r
5149  * Simple plugin for using an Ext.Tip with a slider to show the slider value\r
5150  */\r
5151 Ext.ux.SliderTip = Ext.extend(Ext.Tip, {\r
5152     minWidth: 10,\r
5153     offsets : [0, -10],\r
5154     init : function(slider){\r
5155         slider.on('dragstart', this.onSlide, this);\r
5156         slider.on('drag', this.onSlide, this);\r
5157         slider.on('dragend', this.hide, this);\r
5158         slider.on('destroy', this.destroy, this);\r
5159     },\r
5160 \r
5161     onSlide : function(slider){\r
5162         this.show();\r
5163         this.body.update(this.getText(slider));\r
5164         this.doAutoWidth();\r
5165         this.el.alignTo(slider.thumb, 'b-t?', this.offsets);\r
5166     },\r
5167 \r
5168     getText : function(slider){\r
5169         return String(slider.getValue());\r
5170     }\r
5171 });\r
5172 Ext.ux.SlidingPager = Ext.extend(Object, {\r
5173     init : function(pbar){\r
5174         Ext.each(pbar.items.getRange(2,6), function(c){\r
5175             c.hide();\r
5176         });\r
5177         var slider = new Ext.Slider({\r
5178             width: 114,\r
5179             minValue: 1,\r
5180             maxValue: 1,\r
5181             plugins: new Ext.ux.SliderTip({\r
5182                 getText : function(s){\r
5183                     return String.format('Page <b>{0}</b> of <b>{1}</b>', s.value, s.maxValue);\r
5184                 }\r
5185             }),\r
5186             listeners: {\r
5187                 changecomplete: function(s, v){\r
5188                     pbar.changePage(v);\r
5189                 }\r
5190             }\r
5191         });\r
5192         pbar.insert(5, slider);\r
5193         pbar.on({\r
5194             change: function(pb, data){\r
5195                 slider.maxValue = data.pages;\r
5196                 slider.setValue(data.activePage);\r
5197             },\r
5198             beforedestroy: function(){\r
5199                 slider.destroy();\r
5200             }\r
5201         });\r
5202     }\r
5203 });Ext.ns('Ext.ux.form');\r
5204 \r
5205 /**\r
5206  * @class Ext.ux.form.SpinnerField\r
5207  * @extends Ext.form.NumberField\r
5208  * Creates a field utilizing Ext.ux.Spinner\r
5209  * @xtype spinnerfield\r
5210  */\r
5211 Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {\r
5212     deferHeight: true,\r
5213     autoSize: Ext.emptyFn,\r
5214     onBlur: Ext.emptyFn,\r
5215     adjustSize: Ext.BoxComponent.prototype.adjustSize,\r
5216 \r
5217         constructor: function(config) {\r
5218                 var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');\r
5219 \r
5220                 var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);\r
5221 \r
5222                 var plugins = config.plugins\r
5223                         ? (Ext.isArray(config.plugins)\r
5224                                 ? config.plugins.push(spl)\r
5225                                 : [config.plugins, spl])\r
5226                         : spl;\r
5227 \r
5228                 Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));\r
5229         },\r
5230 \r
5231     onShow: function(){\r
5232         if (this.wrap) {\r
5233             this.wrap.dom.style.display = '';\r
5234             this.wrap.dom.style.visibility = 'visible';\r
5235         }\r
5236     },\r
5237 \r
5238     onHide: function(){\r
5239         this.wrap.dom.style.display = 'none';\r
5240     },\r
5241 \r
5242     // private\r
5243     getResizeEl: function(){\r
5244         return this.wrap;\r
5245     },\r
5246 \r
5247     // private\r
5248     getPositionEl: function(){\r
5249         return this.wrap;\r
5250     },\r
5251 \r
5252     // private\r
5253     alignErrorIcon: function(){\r
5254         if (this.wrap) {\r
5255             this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);\r
5256         }\r
5257     },\r
5258 \r
5259     validateBlur: function(){\r
5260         return true;\r
5261     }\r
5262 });\r
5263 \r
5264 Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);\r
5265 \r
5266 //backwards compat\r
5267 Ext.form.SpinnerField = Ext.ux.form.SpinnerField;\r
5268 /**\r
5269  * @class Ext.ux.Spinner\r
5270  * @extends Ext.util.Observable\r
5271  * Creates a Spinner control utilized by Ext.ux.form.SpinnerField\r
5272  */\r
5273 Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {\r
5274     incrementValue: 1,\r
5275     alternateIncrementValue: 5,\r
5276     triggerClass: 'x-form-spinner-trigger',\r
5277     splitterClass: 'x-form-spinner-splitter',\r
5278     alternateKey: Ext.EventObject.shiftKey,\r
5279     defaultValue: 0,\r
5280     accelerate: false,\r
5281 \r
5282     constructor: function(config){\r
5283         Ext.ux.Spinner.superclass.constructor.call(this, config);\r
5284         Ext.apply(this, config);\r
5285         this.mimicing = false;\r
5286     },\r
5287 \r
5288     init: function(field){\r
5289         this.field = field;\r
5290 \r
5291         field.afterMethod('onRender', this.doRender, this);\r
5292         field.afterMethod('onEnable', this.doEnable, this);\r
5293         field.afterMethod('onDisable', this.doDisable, this);\r
5294         field.afterMethod('afterRender', this.doAfterRender, this);\r
5295         field.afterMethod('onResize', this.doResize, this);\r
5296         field.afterMethod('onFocus', this.doFocus, this);\r
5297         field.beforeMethod('onDestroy', this.doDestroy, this);\r
5298     },\r
5299 \r
5300     doRender: function(ct, position){\r
5301         var el = this.el = this.field.getEl();\r
5302         var f = this.field;\r
5303 \r
5304         if (!f.wrap) {\r
5305             f.wrap = this.wrap = el.wrap({\r
5306                 cls: "x-form-field-wrap"\r
5307             });\r
5308         }\r
5309         else {\r
5310             this.wrap = f.wrap.addClass('x-form-field-wrap');\r
5311         }\r
5312 \r
5313         this.trigger = this.wrap.createChild({\r
5314             tag: "img",\r
5315             src: Ext.BLANK_IMAGE_URL,\r
5316             cls: "x-form-trigger " + this.triggerClass\r
5317         });\r
5318 \r
5319         if (!f.width) {\r
5320             this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());\r
5321         }\r
5322 \r
5323         this.splitter = this.wrap.createChild({\r
5324             tag: 'div',\r
5325             cls: this.splitterClass,\r
5326             style: 'width:13px; height:2px;'\r
5327         });\r
5328         this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();\r
5329 \r
5330         this.proxy = this.trigger.createProxy('', this.splitter, true);\r
5331         this.proxy.addClass("x-form-spinner-proxy");\r
5332         this.proxy.setStyle('left', '0px');\r
5333         this.proxy.setSize(14, 1);\r
5334         this.proxy.hide();\r
5335         this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {\r
5336             dragElId: this.proxy.id\r
5337         });\r
5338 \r
5339         this.initTrigger();\r
5340         this.initSpinner();\r
5341     },\r
5342 \r
5343     doAfterRender: function(){\r
5344         var y;\r
5345         if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {\r
5346             this.el.position();\r
5347             this.el.setY(y);\r
5348         }\r
5349     },\r
5350 \r
5351     doEnable: function(){\r
5352         if (this.wrap) {\r
5353             this.wrap.removeClass(this.field.disabledClass);\r
5354         }\r
5355     },\r
5356 \r
5357     doDisable: function(){\r
5358         if (this.wrap) {\r
5359             this.wrap.addClass(this.field.disabledClass);\r
5360             this.el.removeClass(this.field.disabledClass);\r
5361         }\r
5362     },\r
5363 \r
5364     doResize: function(w, h){\r
5365         if (typeof w == 'number') {\r
5366             this.el.setWidth(this.field.adjustWidth('input', w - this.trigger.getWidth()));\r
5367         }\r
5368         this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());\r
5369     },\r
5370 \r
5371     doFocus: function(){\r
5372         if (!this.mimicing) {\r
5373             this.wrap.addClass('x-trigger-wrap-focus');\r
5374             this.mimicing = true;\r
5375             Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {\r
5376                 delay: 10\r
5377             });\r
5378             this.el.on('keydown', this.checkTab, this);\r
5379         }\r
5380     },\r
5381 \r
5382     // private\r
5383     checkTab: function(e){\r
5384         if (e.getKey() == e.TAB) {\r
5385             this.triggerBlur();\r
5386         }\r
5387     },\r
5388 \r
5389     // private\r
5390     mimicBlur: function(e){\r
5391         if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {\r
5392             this.triggerBlur();\r
5393         }\r
5394     },\r
5395 \r
5396     // private\r
5397     triggerBlur: function(){\r
5398         this.mimicing = false;\r
5399         Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);\r
5400         this.el.un("keydown", this.checkTab, this);\r
5401         this.field.beforeBlur();\r
5402         this.wrap.removeClass('x-trigger-wrap-focus');\r
5403         this.field.onBlur.call(this.field);\r
5404     },\r
5405 \r
5406     initTrigger: function(){\r
5407         this.trigger.addClassOnOver('x-form-trigger-over');\r
5408         this.trigger.addClassOnClick('x-form-trigger-click');\r
5409     },\r
5410 \r
5411     initSpinner: function(){\r
5412         this.field.addEvents({\r
5413             'spin': true,\r
5414             'spinup': true,\r
5415             'spindown': true\r
5416         });\r
5417 \r
5418         this.keyNav = new Ext.KeyNav(this.el, {\r
5419             "up": function(e){\r
5420                 e.preventDefault();\r
5421                 this.onSpinUp();\r
5422             },\r
5423 \r
5424             "down": function(e){\r
5425                 e.preventDefault();\r
5426                 this.onSpinDown();\r
5427             },\r
5428 \r
5429             "pageUp": function(e){\r
5430                 e.preventDefault();\r
5431                 this.onSpinUpAlternate();\r
5432             },\r
5433 \r
5434             "pageDown": function(e){\r
5435                 e.preventDefault();\r
5436                 this.onSpinDownAlternate();\r
5437             },\r
5438 \r
5439             scope: this\r
5440         });\r
5441 \r
5442         this.repeater = new Ext.util.ClickRepeater(this.trigger, {\r
5443             accelerate: this.accelerate\r
5444         });\r
5445         this.field.mon(this.repeater, "click", this.onTriggerClick, this, {\r
5446             preventDefault: true\r
5447         });\r
5448 \r
5449         this.field.mon(this.trigger, {\r
5450             mouseover: this.onMouseOver,\r
5451             mouseout: this.onMouseOut,\r
5452             mousemove: this.onMouseMove,\r
5453             mousedown: this.onMouseDown,\r
5454             mouseup: this.onMouseUp,\r
5455             scope: this,\r
5456             preventDefault: true\r
5457         });\r
5458 \r
5459         this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);\r
5460 \r
5461         this.dd.setXConstraint(0, 0, 10)\r
5462         this.dd.setYConstraint(1500, 1500, 10);\r
5463         this.dd.endDrag = this.endDrag.createDelegate(this);\r
5464         this.dd.startDrag = this.startDrag.createDelegate(this);\r
5465         this.dd.onDrag = this.onDrag.createDelegate(this);\r
5466     },\r
5467 \r
5468     onMouseOver: function(){\r
5469         if (this.disabled) {\r
5470             return;\r
5471         }\r
5472         var middle = this.getMiddle();\r
5473         this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';\r
5474         this.trigger.addClass(this.tmpHoverClass);\r
5475     },\r
5476 \r
5477     //private\r
5478     onMouseOut: function(){\r
5479         this.trigger.removeClass(this.tmpHoverClass);\r
5480     },\r
5481 \r
5482     //private\r
5483     onMouseMove: function(){\r
5484         if (this.disabled) {\r
5485             return;\r
5486         }\r
5487         var middle = this.getMiddle();\r
5488         if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||\r
5489         ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {\r
5490         }\r
5491     },\r
5492 \r
5493     //private\r
5494     onMouseDown: function(){\r
5495         if (this.disabled) {\r
5496             return;\r
5497         }\r
5498         var middle = this.getMiddle();\r
5499         this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';\r
5500         this.trigger.addClass(this.tmpClickClass);\r
5501     },\r
5502 \r
5503     //private\r
5504     onMouseUp: function(){\r
5505         this.trigger.removeClass(this.tmpClickClass);\r
5506     },\r
5507 \r
5508     //private\r
5509     onTriggerClick: function(){\r
5510         if (this.disabled || this.el.dom.readOnly) {\r
5511             return;\r
5512         }\r
5513         var middle = this.getMiddle();\r
5514         var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';\r
5515         this['onSpin' + ud]();\r
5516     },\r
5517 \r
5518     //private\r
5519     getMiddle: function(){\r
5520         var t = this.trigger.getTop();\r
5521         var h = this.trigger.getHeight();\r
5522         var middle = t + (h / 2);\r
5523         return middle;\r
5524     },\r
5525 \r
5526     //private\r
5527     //checks if control is allowed to spin\r
5528     isSpinnable: function(){\r
5529         if (this.disabled || this.el.dom.readOnly) {\r
5530             Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly\r
5531             return false;\r
5532         }\r
5533         return true;\r
5534     },\r
5535 \r
5536     handleMouseWheel: function(e){\r
5537         //disable scrolling when not focused\r
5538         if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {\r
5539             return;\r
5540         }\r
5541 \r
5542         var delta = e.getWheelDelta();\r
5543         if (delta > 0) {\r
5544             this.onSpinUp();\r
5545             e.stopEvent();\r
5546         }\r
5547         else\r
5548             if (delta < 0) {\r
5549                 this.onSpinDown();\r
5550                 e.stopEvent();\r
5551             }\r
5552     },\r
5553 \r
5554     //private\r
5555     startDrag: function(){\r
5556         this.proxy.show();\r
5557         this._previousY = Ext.fly(this.dd.getDragEl()).getTop();\r
5558     },\r
5559 \r
5560     //private\r
5561     endDrag: function(){\r
5562         this.proxy.hide();\r
5563     },\r
5564 \r
5565     //private\r
5566     onDrag: function(){\r
5567         if (this.disabled) {\r
5568             return;\r
5569         }\r
5570         var y = Ext.fly(this.dd.getDragEl()).getTop();\r
5571         var ud = '';\r
5572 \r
5573         if (this._previousY > y) {\r
5574             ud = 'Up';\r
5575         } //up\r
5576         if (this._previousY < y) {\r
5577             ud = 'Down';\r
5578         } //down\r
5579         if (ud != '') {\r
5580             this['onSpin' + ud]();\r
5581         }\r
5582 \r
5583         this._previousY = y;\r
5584     },\r
5585 \r
5586     //private\r
5587     onSpinUp: function(){\r
5588         if (this.isSpinnable() == false) {\r
5589             return;\r
5590         }\r
5591         if (Ext.EventObject.shiftKey == true) {\r
5592             this.onSpinUpAlternate();\r
5593             return;\r
5594         }\r
5595         else {\r
5596             this.spin(false, false);\r
5597         }\r
5598         this.field.fireEvent("spin", this);\r
5599         this.field.fireEvent("spinup", this);\r
5600     },\r
5601 \r
5602     //private\r
5603     onSpinDown: function(){\r
5604         if (this.isSpinnable() == false) {\r
5605             return;\r
5606         }\r
5607         if (Ext.EventObject.shiftKey == true) {\r
5608             this.onSpinDownAlternate();\r
5609             return;\r
5610         }\r
5611         else {\r
5612             this.spin(true, false);\r
5613         }\r
5614         this.field.fireEvent("spin", this);\r
5615         this.field.fireEvent("spindown", this);\r
5616     },\r
5617 \r
5618     //private\r
5619     onSpinUpAlternate: function(){\r
5620         if (this.isSpinnable() == false) {\r
5621             return;\r
5622         }\r
5623         this.spin(false, true);\r
5624         this.field.fireEvent("spin", this);\r
5625         this.field.fireEvent("spinup", this);\r
5626     },\r
5627 \r
5628     //private\r
5629     onSpinDownAlternate: function(){\r
5630         if (this.isSpinnable() == false) {\r
5631             return;\r
5632         }\r
5633         this.spin(true, true);\r
5634         this.field.fireEvent("spin", this);\r
5635         this.field.fireEvent("spindown", this);\r
5636     },\r
5637 \r
5638     spin: function(down, alternate){\r
5639         var v = parseFloat(this.field.getValue());\r
5640         var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;\r
5641         (down == true) ? v -= incr : v += incr;\r
5642 \r
5643         v = (isNaN(v)) ? this.defaultValue : v;\r
5644         v = this.fixBoundries(v);\r
5645         this.field.setRawValue(v);\r
5646     },\r
5647 \r
5648     fixBoundries: function(value){\r
5649         var v = value;\r
5650 \r
5651         if (this.field.minValue != undefined && v < this.field.minValue) {\r
5652             v = this.field.minValue;\r
5653         }\r
5654         if (this.field.maxValue != undefined && v > this.field.maxValue) {\r
5655             v = this.field.maxValue;\r
5656         }\r
5657 \r
5658         return this.fixPrecision(v);\r
5659     },\r
5660 \r
5661     // private\r
5662     fixPrecision: function(value){\r
5663         var nan = isNaN(value);\r
5664         if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {\r
5665             return nan ? '' : value;\r
5666         }\r
5667         return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));\r
5668     },\r
5669 \r
5670     doDestroy: function(){\r
5671         if (this.trigger) {\r
5672             this.trigger.remove();\r
5673         }\r
5674         if (this.wrap) {\r
5675             this.wrap.remove();\r
5676             delete this.field.wrap;\r
5677         }\r
5678 \r
5679         if (this.splitter) {\r
5680             this.splitter.remove();\r
5681         }\r
5682 \r
5683         if (this.dd) {\r
5684             this.dd.unreg();\r
5685             this.dd = null;\r
5686         }\r
5687 \r
5688         if (this.proxy) {\r
5689             this.proxy.remove();\r
5690         }\r
5691 \r
5692         if (this.repeater) {\r
5693             this.repeater.purgeListeners();\r
5694         }\r
5695     }\r
5696 });\r
5697 \r
5698 //backwards compat\r
5699 Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){\r
5700     Ext.apply(this, config);\r
5701 }\r
5702 Ext.ux.Spotlight.prototype = {\r
5703     active : false,\r
5704     animate : true,\r
5705     duration: .25,\r
5706     easing:'easeNone',\r
5707 \r
5708     // private\r
5709     animated : false,\r
5710 \r
5711     createElements : function(){\r
5712         var bd = Ext.getBody();\r
5713 \r
5714         this.right = bd.createChild({cls:'x-spotlight'});\r
5715         this.left = bd.createChild({cls:'x-spotlight'});\r
5716         this.top = bd.createChild({cls:'x-spotlight'});\r
5717         this.bottom = bd.createChild({cls:'x-spotlight'});\r
5718 \r
5719         this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);\r
5720     },\r
5721 \r
5722     show : function(el, callback, scope){\r
5723         if(this.animated){\r
5724             this.show.defer(50, this, [el, callback, scope]);\r
5725             return;\r
5726         }\r
5727         this.el = Ext.get(el);\r
5728         if(!this.right){\r
5729             this.createElements();\r
5730         }\r
5731         if(!this.active){\r
5732             this.all.setDisplayed('');\r
5733             this.applyBounds(true, false);\r
5734             this.active = true;\r
5735             Ext.EventManager.onWindowResize(this.syncSize, this);\r
5736             this.applyBounds(false, this.animate, false, callback, scope);\r
5737         }else{\r
5738             this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous\r
5739         }\r
5740     },\r
5741 \r
5742     hide : function(callback, scope){\r
5743         if(this.animated){\r
5744             this.hide.defer(50, this, [callback, scope]);\r
5745             return;\r
5746         }\r
5747         Ext.EventManager.removeResizeListener(this.syncSize, this);\r
5748         this.applyBounds(true, this.animate, true, callback, scope);\r
5749     },\r
5750 \r
5751     doHide : function(){\r
5752         this.active = false;\r
5753         this.all.setDisplayed(false);\r
5754     },\r
5755 \r
5756     syncSize : function(){\r
5757         this.applyBounds(false, false);\r
5758     },\r
5759 \r
5760     applyBounds : function(basePts, anim, doHide, callback, scope){\r
5761 \r
5762         var rg = this.el.getRegion();\r
5763 \r
5764         var dw = Ext.lib.Dom.getViewWidth(true);\r
5765         var dh = Ext.lib.Dom.getViewHeight(true);\r
5766 \r
5767         var c = 0, cb = false;\r
5768         if(anim){\r
5769             cb = {\r
5770                 callback: function(){\r
5771                     c++;\r
5772                     if(c == 4){\r
5773                         this.animated = false;\r
5774                         if(doHide){\r
5775                             this.doHide();\r
5776                         }\r
5777                         Ext.callback(callback, scope, [this]);\r
5778                     }\r
5779                 },\r
5780                 scope: this,\r
5781                 duration: this.duration,\r
5782                 easing: this.easing\r
5783             };\r
5784             this.animated = true;\r
5785         }\r
5786 \r
5787         this.right.setBounds(\r
5788                 rg.right,\r
5789                 basePts ? dh : rg.top,\r
5790                 dw - rg.right,\r
5791                 basePts ? 0 : (dh - rg.top),\r
5792                 cb);\r
5793 \r
5794         this.left.setBounds(\r
5795                 0,\r
5796                 0,\r
5797                 rg.left,\r
5798                 basePts ? 0 : rg.bottom,\r
5799                 cb);\r
5800 \r
5801         this.top.setBounds(\r
5802                 basePts ? dw : rg.left,\r
5803                 0,\r
5804                 basePts ? 0 : dw - rg.left,\r
5805                 rg.top,\r
5806                 cb);\r
5807 \r
5808         this.bottom.setBounds(\r
5809                 0,\r
5810                 rg.bottom,\r
5811                 basePts ? 0 : rg.right,\r
5812                 dh - rg.bottom,\r
5813                 cb);\r
5814 \r
5815         if(!anim){\r
5816             if(doHide){\r
5817                 this.doHide();\r
5818             }\r
5819             if(callback){\r
5820                 Ext.callback(callback, scope, [this]);\r
5821             }\r
5822         }\r
5823     },\r
5824 \r
5825     destroy : function(){\r
5826         this.doHide();\r
5827         Ext.destroy(\r
5828             this.right,\r
5829             this.left,\r
5830             this.top,\r
5831             this.bottom);\r
5832         delete this.el;\r
5833         delete this.all;\r
5834     }\r
5835 };\r
5836 \r
5837 //backwards compat\r
5838 Ext.Spotlight = Ext.ux.Spotlight;/**\r
5839  * @class Ext.ux.TabCloseMenu\r
5840  * @extends Object \r
5841  * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.\r
5842  * \r
5843  * @ptype tabclosemenu\r
5844  */\r
5845 Ext.ux.TabCloseMenu = function(){\r
5846     var tabs, menu, ctxItem;\r
5847     this.init = function(tp){\r
5848         tabs = tp;\r
5849         tabs.on('contextmenu', onContextMenu);\r
5850     };\r
5851 \r
5852     function onContextMenu(ts, item, e){\r
5853         if(!menu){ // create context menu on first right click\r
5854             menu = new Ext.menu.Menu({            \r
5855             items: [{\r
5856                 id: tabs.id + '-close',\r
5857                 text: 'Close Tab',\r
5858                 handler : function(){\r
5859                     tabs.remove(ctxItem);\r
5860                 }\r
5861             },{\r
5862                 id: tabs.id + '-close-others',\r
5863                 text: 'Close Other Tabs',\r
5864                 handler : function(){\r
5865                     tabs.items.each(function(item){\r
5866                         if(item.closable && item != ctxItem){\r
5867                             tabs.remove(item);\r
5868                         }\r
5869                     });\r
5870                 }\r
5871             }]});\r
5872         }\r
5873         ctxItem = item;\r
5874         var items = menu.items;\r
5875         items.get(tabs.id + '-close').setDisabled(!item.closable);\r
5876         var disableOthers = true;\r
5877         tabs.items.each(function(){\r
5878             if(this != item && this.closable){\r
5879                 disableOthers = false;\r
5880                 return false;\r
5881             }\r
5882         });\r
5883         items.get(tabs.id + '-close-others').setDisabled(disableOthers);\r
5884         e.stopEvent();\r
5885         menu.showAt(e.getPoint());\r
5886     }\r
5887 };\r
5888 \r
5889 Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);\r
5890 Ext.ns('Ext.ux.grid');
5891
5892 /**
5893  * @class Ext.ux.grid.TableGrid
5894  * @extends Ext.grid.GridPanel
5895  * A Grid which creates itself from an existing HTML table element.
5896  * @history
5897  * 2007-03-01 Original version by Nige "Animal" White
5898  * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor
5899  * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
5900  * The table MUST have some type of size defined for the grid to fill. The container will be
5901  * automatically set to position relative if it isn't already.
5902  * @param {Object} config A config object that sets properties on this grid and has two additional (optional)
5903  * properties: fields and columns which allow for customizing data fields and columns for this grid.
5904  */
5905 Ext.ux.grid.TableGrid = function(table, config){
5906     config = config ||
5907     {};
5908     Ext.apply(this, config);
5909     var cf = config.fields || [], ch = config.columns || [];
5910     table = Ext.get(table);
5911     
5912     var ct = table.insertSibling();
5913     
5914     var fields = [], cols = [];
5915     var headers = table.query("thead th");
5916     for (var i = 0, h; h = headers[i]; i++) {
5917         var text = h.innerHTML;
5918         var name = 'tcol-' + i;
5919         
5920         fields.push(Ext.applyIf(cf[i] ||
5921         {}, {
5922             name: name,
5923             mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
5924         }));
5925         
5926         cols.push(Ext.applyIf(ch[i] ||
5927         {}, {
5928             'header': text,
5929             'dataIndex': name,
5930             'width': h.offsetWidth,
5931             'tooltip': h.title,
5932             'sortable': true
5933         }));
5934     }
5935     
5936     var ds = new Ext.data.Store({
5937         reader: new Ext.data.XmlReader({
5938             record: 'tbody tr'
5939         }, fields)
5940     });
5941     
5942     ds.loadData(table.dom);
5943     
5944     var cm = new Ext.grid.ColumnModel(cols);
5945     
5946     if (config.width || config.height) {
5947         ct.setSize(config.width || 'auto', config.height || 'auto');
5948     }
5949     else {
5950         ct.setWidth(table.getWidth());
5951     }
5952     
5953     if (config.remove !== false) {
5954         table.remove();
5955     }
5956     
5957     Ext.applyIf(this, {
5958         'ds': ds,
5959         'cm': cm,
5960         'sm': new Ext.grid.RowSelectionModel(),
5961         autoHeight: true,
5962         autoWidth: false
5963     });
5964     Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {});
5965 };
5966
5967 Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
5968
5969 //backwards compat
5970 Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
5971
5972
5973 Ext.ux.TabScrollerMenu =  Ext.extend(Object, {
5974         pageSize       : 10,
5975         maxText        : 15,
5976         menuPrefixText : 'Items',
5977         constructor    : function(config) {
5978                 config = config || {};
5979                 Ext.apply(this, config);
5980         },
5981         init : function(tabPanel) {
5982                 Ext.apply(tabPanel, this.tabPanelMethods);
5983                 
5984                 tabPanel.tabScrollerMenu = this;
5985                 var thisRef = this;
5986                 
5987                 tabPanel.on({
5988                         render : {
5989                                 scope  : tabPanel,
5990                                 single : true,
5991                                 fn     : function() { 
5992                                         var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
5993                                         tabPanel.createScrollers = newFn;
5994                                 }
5995                         }
5996                 });
5997         },
5998         // private && sequeneced
5999         createPanelsMenu : function() {
6000                 var h = this.stripWrap.dom.offsetHeight;
6001                 
6002                 //move the right menu item to the left 18px
6003                 var rtScrBtn = this.header.dom.firstChild;
6004                 Ext.fly(rtScrBtn).applyStyles({
6005                         right : '18px'
6006                 });
6007                 
6008                 var stripWrap = Ext.get(this.strip.dom.parentNode);
6009                 stripWrap.applyStyles({
6010                          'margin-right' : '36px'
6011                 });
6012                 
6013                 // Add the new righthand menu
6014                 var scrollMenu = this.header.insertFirst({
6015                         cls:'x-tab-tabmenu-right'
6016                 });
6017                 scrollMenu.setHeight(h);
6018                 scrollMenu.addClassOnOver('x-tab-tabmenu-over');
6019                 scrollMenu.on('click', this.showTabsMenu, this);        
6020                 
6021                 this.scrollLeft.show = this.scrollLeft.show.createSequence(function() {
6022                         scrollMenu.show();                                                                                                                                               
6023                 });
6024                 
6025                 this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() {
6026                         scrollMenu.hide();                                                              
6027                 });
6028                 
6029         },
6030         // public
6031         getPageSize : function() {
6032                 return this.pageSize;
6033         },
6034         // public
6035         setPageSize : function(pageSize) {
6036                 this.pageSize = pageSize;
6037         },
6038         // public
6039         getMaxText : function() {
6040                 return this.maxText;
6041         },
6042         // public
6043         setMaxText : function(t) {
6044                 this.maxText = t;
6045         },
6046         getMenuPrefixText : function() {
6047                 return this.menuPrefixText;
6048         },
6049         setMenuPrefixText : function(t) {
6050                 this.menuPrefixText = t;
6051         },
6052         // private && applied to the tab panel itself.
6053         tabPanelMethods : {
6054                 // all execute within the scope of the tab panel
6055                 // private      
6056                 showTabsMenu : function(e) {            
6057                         if (! this.tabsMenu) {
6058                                 this.tabsMenu =  new Ext.menu.Menu();
6059                                 this.on('beforedestroy', this.tabsMenu.destroy, this.tabsMenu);
6060                         }
6061                         
6062                         this.tabsMenu.removeAll();
6063                         
6064                         this.generateTabMenuItems();
6065                         
6066                         var target = Ext.get(e.getTarget());
6067                         var xy     = target.getXY();
6068                         
6069                         //Y param + 24 pixels
6070                         xy[1] += 24;
6071                         
6072                         this.tabsMenu.showAt(xy);
6073                 },
6074                 // private      
6075                 generateTabMenuItems : function() {
6076                         var curActive  = this.getActiveTab();
6077                         var totalItems = this.items.getCount();
6078                         var pageSize   = this.tabScrollerMenu.getPageSize();
6079                         
6080                         
6081                         if (totalItems > pageSize)  {
6082                                 var numSubMenus = Math.floor(totalItems / pageSize);
6083                                 var remainder   = totalItems % pageSize;
6084                                 
6085                                 // Loop through all of the items and create submenus in chunks of 10
6086                                 for (var i = 0 ; i < numSubMenus; i++) {
6087                                         var curPage = (i + 1) * pageSize;
6088                                         var menuItems = [];
6089                                         
6090                                         
6091                                         for (var x = 0; x < pageSize; x++) {                            
6092                                                 index = x + curPage - pageSize;
6093                                                 var item = this.items.get(index);
6094                                                 menuItems.push(this.autoGenMenuItem(item));
6095                                         }
6096                                         
6097                                         this.tabsMenu.add({
6098                                                 text : this.tabScrollerMenu.getMenuPrefixText() + ' '  + (curPage - pageSize + 1) + ' - ' + curPage,
6099                                                 menu : menuItems
6100                                         });
6101                                         
6102                                 }
6103                                 // remaining items
6104                                 if (remainder > 0) {
6105                                         var start = numSubMenus * pageSize;
6106                                         menuItems = [];
6107                                         for (var i = start ; i < totalItems; i ++ ) {                                   
6108                                                 var item = this.items.get(i);
6109                                                 menuItems.push(this.autoGenMenuItem(item));
6110                                         }
6111                                         
6112                                         
6113                                         this.tabsMenu.add({
6114                                                 text : this.tabScrollerMenu.menuPrefixText  + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
6115                                                 menu : menuItems
6116                                         });
6117                                         
6118
6119                                 }
6120                         }
6121                         else {
6122                                 this.items.each(function(item) {
6123                                         if (item.id != curActive.id && ! item.hidden) {
6124                                                 menuItems.push(this.autoGenMenuItem(item));
6125                                         }
6126                                 }, this);
6127                         }       
6128                 },
6129                 // private
6130                 autoGenMenuItem : function(item) {
6131                         var maxText = this.tabScrollerMenu.getMaxText();
6132                         var text    = Ext.util.Format.ellipsis(item.title, maxText);
6133                         
6134                         return {
6135                                 text      : text,
6136                                 handler   : this.showTabFromMenu,
6137                                 scope     : this,
6138                                 disabled  : item.disabled,
6139                                 tabToShow : item,
6140                                 iconCls   : item.iconCls
6141                         }
6142                 
6143                 },
6144                 // private
6145                 showTabFromMenu : function(menuItem) {
6146                         this.setActiveTab(menuItem.tabToShow);
6147                 }       
6148         }       
6149 });
6150 Ext.ns('Ext.ux.tree');
6151
6152 /**
6153  * @class Ext.ux.tree.XmlTreeLoader
6154  * @extends Ext.tree.TreeLoader
6155  * <p>A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s.
6156  * Any text value included as a text node in the XML will be added to the parent node as an attribute
6157  * called <tt>innerText</tt>.  Also, the tag name of each XML node will be added to the tree node as
6158  * an attribute called <tt>tagName</tt>.</p>
6159  * <p>By default, this class expects that your source XML will provide the necessary attributes on each
6160  * node as expected by the {@link Ext.tree.TreePanel} to display and load properly.  However, you can
6161  * provide your own custom processing of node attributes by overriding the {@link #processNode} method
6162  * and modifying the attributes as needed before they are used to create the associated TreeNode.</p>
6163  * @constructor
6164  * Creates a new XmlTreeloader.
6165  * @param {Object} config A config object containing config properties.
6166  */
6167 Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
6168     /**
6169      * @property  XML_NODE_ELEMENT
6170      * XML element node (value 1, read-only)
6171      * @type Number
6172      */
6173     XML_NODE_ELEMENT : 1,
6174     /**
6175      * @property  XML_NODE_TEXT
6176      * XML text node (value 3, read-only)
6177      * @type Number
6178      */
6179     XML_NODE_TEXT : 3,
6180
6181     // private override
6182     processResponse : function(response, node, callback){
6183         var xmlData = response.responseXML;
6184         var root = xmlData.documentElement || xmlData;
6185
6186         try{
6187             node.beginUpdate();
6188             node.appendChild(this.parseXml(root));
6189             node.endUpdate();
6190
6191             if(typeof callback == "function"){
6192                 callback(this, node);
6193             }
6194         }catch(e){
6195             this.handleFailure(response);
6196         }
6197     },
6198
6199     // private
6200     parseXml : function(node) {
6201         var nodes = [];
6202         Ext.each(node.childNodes, function(n){
6203             if(n.nodeType == this.XML_NODE_ELEMENT){
6204                 var treeNode = this.createNode(n);
6205                 if(n.childNodes.length > 0){
6206                     var child = this.parseXml(n);
6207                     if(typeof child == 'string'){
6208                         treeNode.attributes.innerText = child;
6209                     }else{
6210                         treeNode.appendChild(child);
6211                     }
6212                 }
6213                 nodes.push(treeNode);
6214             }
6215             else if(n.nodeType == this.XML_NODE_TEXT){
6216                 var text = n.nodeValue.trim();
6217                 if(text.length > 0){
6218                     return nodes = text;
6219                 }
6220             }
6221         }, this);
6222
6223         return nodes;
6224     },
6225
6226     // private override
6227     createNode : function(node){
6228         var attr = {
6229             tagName: node.tagName
6230         };
6231
6232         Ext.each(node.attributes, function(a){
6233             attr[a.nodeName] = a.nodeValue;
6234         });
6235
6236         this.processAttributes(attr);
6237
6238         return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
6239     },
6240
6241     /*
6242      * Template method intended to be overridden by subclasses that need to provide
6243      * custom attribute processing prior to the creation of each TreeNode.  This method
6244      * will be passed a config object containing existing TreeNode attribute name/value
6245      * pairs which can be modified as needed directly (no need to return the object).
6246      */
6247     processAttributes: Ext.emptyFn
6248 });
6249
6250 //backwards compat
6251 Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;