source: trunk/web/addons/job_monarch/lib/extjs-30/examples/ux/ux-all-debug.js @ 625

Last change on this file since 625 was 625, checked in by ramonb, 15 years ago

lib/extjs-30:

  • new ExtJS 3.0
File size: 193.0 KB
Line 
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 */
7Ext.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 */
14Ext.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:
215Ext.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
229var 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.
242var 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 */
254Ext.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
268Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout;
269Ext.ns('Ext.ux.grid');
270
271/**
272 * @class Ext.ux.grid.CheckColumn
273 * @extends Object
274 * GridPanel plugin to add a column with check boxes to a grid.
275 * <p>Example usage:</p>
276 * <pre><code>
277// create the column
278var checkColumn = new Ext.grid.CheckColumn({
279   header: 'Indoor?',
280   dataIndex: 'indoor',
281   id: 'check',
282   width: 55
283});
284
285// add the column to the column model
286var cm = new Ext.grid.ColumnModel([{
287       header: 'Foo',
288       ...
289    },
290    checkColumn
291]);
292
293// create the grid
294var grid = new Ext.grid.EditorGridPanel({
295    ...
296    cm: cm,
297    plugins: [checkColumn], // include plugin
298    ...
299});
300 * </code></pre>
301 * In addition to storing a Boolean value within the record data, this
302 * class toggles a css class between <tt>'x-grid3-check-col'</tt> and
303 * <tt>'x-grid3-check-col-on'</tt> to alter the background image used for
304 * a column.
305 */
306Ext.ux.grid.CheckColumn = function(config){
307    Ext.apply(this, config);
308    if(!this.id){
309        this.id = Ext.id();
310    }
311    this.renderer = this.renderer.createDelegate(this);
312};
313
314Ext.ux.grid.CheckColumn.prototype ={
315    init : function(grid){
316        this.grid = grid;
317        this.grid.on('render', function(){
318            var view = this.grid.getView();
319            view.mainBody.on('mousedown', this.onMouseDown, this);
320        }, this);
321    },
322
323    onMouseDown : function(e, t){
324        if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){
325            e.stopEvent();
326            var index = this.grid.getView().findRowIndex(t);
327            var record = this.grid.store.getAt(index);
328            record.set(this.dataIndex, !record.data[this.dataIndex]);
329        }
330    },
331
332    renderer : function(v, p, record){
333        p.css += ' x-grid3-check-col-td'; 
334        return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'">&#160;</div>';
335    }
336};
337
338// register ptype
339Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);
340
341// backwards compat
342Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.tree');
343
344/**
345 * @class Ext.ux.tree.ColumnTree
346 * @extends Ext.tree.TreePanel
347 *
348 * @xtype columntree
349 */
350Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {
351    lines : false,
352    borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
353    cls : 'x-column-tree',
354
355    onRender : function(){
356        Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);
357        this.headers = this.header.createChild({cls:'x-tree-headers'});
358
359        var cols = this.columns, c;
360        var totalWidth = 0;
361        var scrollOffset = 19; // similar to Ext.grid.GridView default
362
363        for(var i = 0, len = cols.length; i < len; i++){
364             c = cols[i];
365             totalWidth += c.width;
366             this.headers.createChild({
367                 cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
368                 cn: {
369                     cls:'x-tree-hd-text',
370                     html: c.header
371                 },
372                 style:'width:'+(c.width-this.borderWidth)+'px;'
373             });
374        }
375        this.headers.createChild({cls:'x-clear'});
376        // prevent floats from wrapping when clipped
377        this.headers.setWidth(totalWidth+scrollOffset);
378        this.innerCt.setWidth(totalWidth);
379    }
380});
381
382Ext.reg('columntree', Ext.ux.tree.ColumnTree);
383
384//backwards compat
385Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree;
386
387
388/**
389 * @class Ext.ux.tree.ColumnNodeUI
390 * @extends Ext.tree.TreeNodeUI
391 */
392Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
393    focus: Ext.emptyFn, // prevent odd scrolling behavior
394
395    renderElements : function(n, a, targetNode, bulkRender){
396        this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
397
398        var t = n.getOwnerTree();
399        var cols = t.columns;
400        var bw = t.borderWidth;
401        var c = cols[0];
402
403        var buf = [
404             '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',
405                '<div class="x-tree-col" style="width:',c.width-bw,'px;">',
406                    '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
407                    '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
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">',
409                    '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',
410                    a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
411                    '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",
412                "</div>"];
413         for(var i = 1, len = cols.length; i < len; i++){
414             c = cols[i];
415
416             buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',
417                        '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",
418                      "</div>");
419         }
420         buf.push(
421            '<div class="x-clear"></div></div>',
422            '<ul class="x-tree-node-ct" style="display:none;"></ul>',
423            "</li>");
424
425        if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
426            this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
427                                n.nextSibling.ui.getEl(), buf.join(""));
428        }else{
429            this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
430        }
431
432        this.elNode = this.wrap.childNodes[0];
433        this.ctNode = this.wrap.childNodes[1];
434        var cs = this.elNode.firstChild.childNodes;
435        this.indentNode = cs[0];
436        this.ecNode = cs[1];
437        this.iconNode = cs[2];
438        this.anchor = cs[3];
439        this.textNode = cs[3].firstChild;
440    }
441});
442
443//backwards compat
444Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI;
445/**
446 * @class Ext.DataView.LabelEditor
447 * @extends Ext.Editor
448 *
449 */
450Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, {
451    alignment: "tl-tl",
452    hideEl : false,
453    cls: "x-small-editor",
454    shim: false,
455    completeOnEnter: true,
456    cancelOnEsc: true,
457    labelSelector: 'span.x-editable',
458   
459    constructor: function(cfg, field){
460        Ext.DataView.LabelEditor.superclass.constructor.call(this,
461            field || new Ext.form.TextField({
462                allowBlank: false,
463                growMin:90,
464                growMax:240,
465                grow:true,
466                selectOnFocus:true
467            }), cfg
468        );
469    },
470   
471    init : function(view){
472        this.view = view;
473        view.on('render', this.initEditor, this);
474        this.on('complete', this.onSave, this);
475    },
476
477    initEditor : function(){
478        this.view.on({
479            scope: this,
480            containerclick: this.doBlur,
481            click: this.doBlur
482        });
483        this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});
484    },
485   
486    doBlur: function(){
487        if(this.editing){
488            this.field.blur();
489        }
490    },
491
492    onMouseDown : function(e, target){
493        if(!e.ctrlKey && !e.shiftKey){
494            var item = this.view.findItemFromChild(target);
495            e.stopEvent();
496            var record = this.view.store.getAt(this.view.indexOf(item));
497            this.startEdit(target, record.data[this.dataIndex]);
498            this.activeRecord = record;
499        }else{
500            e.preventDefault();
501        }
502    },
503
504    onSave : function(ed, value){
505        this.activeRecord.set(this.dataIndex, value);
506    }
507});
508
509
510Ext.DataView.DragSelector = function(cfg){
511    cfg = cfg || {};
512    var view, proxy, tracker;
513    var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);
514    var dragSafe = cfg.dragSafe === true;
515
516    this.init = function(dataView){
517        view = dataView;
518        view.on('render', onRender);
519    };
520
521    function fillRegions(){
522        rs = [];
523        view.all.each(function(el){
524            rs[rs.length] = el.getRegion();
525        });
526        bodyRegion = view.el.getRegion();
527    }
528
529    function cancelClick(){
530        return false;
531    }
532
533    function onBeforeStart(e){
534        return !dragSafe || e.target == view.el.dom;
535    }
536
537    function onStart(e){
538        view.on('containerclick', cancelClick, view, {single:true});
539        if(!proxy){
540            proxy = view.el.createChild({cls:'x-view-selector'});
541        }else{
542            proxy.setDisplayed('block');
543        }
544        fillRegions();
545        view.clearSelections();
546    }
547
548    function onDrag(e){
549        var startXY = tracker.startXY;
550        var xy = tracker.getXY();
551
552        var x = Math.min(startXY[0], xy[0]);
553        var y = Math.min(startXY[1], xy[1]);
554        var w = Math.abs(startXY[0] - xy[0]);
555        var h = Math.abs(startXY[1] - xy[1]);
556
557        dragRegion.left = x;
558        dragRegion.top = y;
559        dragRegion.right = x+w;
560        dragRegion.bottom = y+h;
561
562        dragRegion.constrainTo(bodyRegion);
563        proxy.setRegion(dragRegion);
564
565        for(var i = 0, len = rs.length; i < len; i++){
566            var r = rs[i], sel = dragRegion.intersect(r);
567            if(sel && !r.selected){
568                r.selected = true;
569                view.select(i, true);
570            }else if(!sel && r.selected){
571                r.selected = false;
572                view.deselect(i);
573            }
574        }
575    }
576
577    function onEnd(e){
578        if (!Ext.isIE) {
579            view.un('containerclick', cancelClick, view);   
580        }       
581        if(proxy){
582            proxy.setDisplayed(false);
583        }
584    }
585
586    function onRender(view){
587        tracker = new Ext.dd.DragTracker({
588            onBeforeStart: onBeforeStart,
589            onStart: onStart,
590            onDrag: onDrag,
591            onEnd: onEnd
592        });
593        tracker.initEl(view.el);
594    }
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 */
603Ext.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
730Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
731
732// backwards compat
733Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
734(function(){
735Ext.ns('Ext.a11y');
736
737Ext.a11y.Frame = Ext.extend(Object, {
738    initialized: false,
739   
740    constructor: function(size, color){
741        this.setSize(size || 1);
742        this.setColor(color || '15428B');
743    },
744   
745    init: function(){
746        if (!this.initialized) {
747            this.sides = [];
748           
749            var s, i;
750           
751            this.ct = Ext.DomHelper.append(document.body, {
752                cls: 'x-a11y-focusframe'
753            }, true);
754           
755            for (i = 0; i < 4; i++) {
756                s = Ext.DomHelper.append(this.ct, {
757                    cls: 'x-a11y-focusframe-side',
758                    style: 'background-color: #' + this.color
759                }, true);
760                s.visibilityMode = Ext.Element.DISPLAY;
761                this.sides.push(s);
762            }
763           
764            this.frameTask = new Ext.util.DelayedTask(function(el){
765                var newEl = Ext.get(el);
766                if (newEl != this.curEl) {
767                    var w = newEl.getWidth();
768                    var h = newEl.getHeight();
769                    this.sides[0].show().setSize(w, this.size).anchorTo(el, 'tl', [0, -1]);
770                    this.sides[2].show().setSize(w, this.size).anchorTo(el, 'bl', [0, -1]);
771                    this.sides[1].show().setSize(this.size, h).anchorTo(el, 'tr', [-1, 0]);
772                    this.sides[3].show().setSize(this.size, h).anchorTo(el, 'tl', [-1, 0]);
773                    this.curEl = newEl;
774                }
775            }, this);
776           
777            this.unframeTask = new Ext.util.DelayedTask(function(){
778                if (this.initialized) {
779                    this.sides[0].hide();
780                    this.sides[1].hide();
781                    this.sides[2].hide();
782                    this.sides[3].hide();
783                    this.curEl = null;
784                }
785            }, this);
786            this.initialized = true;
787        }
788    },
789   
790    frame: function(el){
791        this.init();
792        this.unframeTask.cancel();
793        this.frameTask.delay(2, false, false, [el]);
794    },
795   
796    unframe: function(){
797        this.init();
798        this.unframeTask.delay(2);
799    },
800   
801    setSize: function(size){
802        this.size = size;
803    },
804   
805    setColor: function(color){
806        this.color = color;
807    }
808});
809
810Ext.a11y.FocusFrame = new Ext.a11y.Frame(2, '15428B');
811Ext.a11y.RelayFrame = new Ext.a11y.Frame(1, '6B8CBF');
812
813Ext.a11y.Focusable = Ext.extend(Ext.util.Observable, {
814    constructor: function(el, relayTo, noFrame, frameEl){
815        Ext.a11y.Focusable.superclass.constructor.call(this);
816       
817        this.addEvents('focus', 'blur', 'left', 'right', 'up', 'down', 'esc', 'enter', 'space');
818       
819        if (el instanceof Ext.Component) {
820            this.el = el.el;
821            this.setComponent(el);
822        }
823        else {
824            this.el = Ext.get(el);
825            this.setComponent(null);
826        }
827       
828        this.setRelayTo(relayTo)
829        this.setNoFrame(noFrame);
830        this.setFrameEl(frameEl);
831       
832        this.init();
833       
834        Ext.a11y.FocusMgr.register(this);
835    },
836   
837    init: function(){
838        this.el.dom.tabIndex = '1';
839        this.el.addClass('x-a11y-focusable');
840        this.el.on({
841            focus: this.onFocus,
842            blur: this.onBlur,
843            keydown: this.onKeyDown,
844            scope: this
845        });
846    },
847   
848    setRelayTo: function(relayTo){
849        this.relayTo = relayTo ? Ext.a11y.FocusMgr.get(relayTo) : null;
850    },
851   
852    setNoFrame: function(noFrame){
853        this.noFrame = (noFrame === true) ? true : false;
854    },
855   
856    setFrameEl: function(frameEl){
857        this.frameEl = frameEl && Ext.get(frameEl) || this.el;
858    },
859   
860    setComponent: function(cmp){
861        this.component = cmp || null;
862    },
863   
864    onKeyDown: function(e, t){
865        var k = e.getKey(), SK = Ext.a11y.Focusable.SpecialKeys, ret, tf;
866       
867        tf = (t !== this.el.dom) ? Ext.a11y.FocusMgr.get(t, true) : this;
868        if (!tf) {
869            // this can happen when you are on a focused item within a panel body
870            // that is not a Ext.a11y.Focusable
871            tf = Ext.a11y.FocusMgr.get(Ext.fly(t).parent('.x-a11y-focusable'));
872        }
873       
874        if (SK[k] !== undefined) {
875            ret = this.fireEvent(SK[k], e, t, tf, this);
876        }
877        if (ret === false || this.fireEvent('keydown', e, t, tf, this) === false) {
878            e.stopEvent();
879        }
880    },
881   
882    focus: function(){
883        this.el.dom.focus();
884    },
885   
886    blur: function(){
887        this.el.dom.blur();
888    },
889   
890    onFocus: function(e, t){
891        this.el.addClass('x-a11y-focused');
892        if (this.relayTo) {
893            this.relayTo.el.addClass('x-a11y-focused-relay');
894            if (!this.relayTo.noFrame) {
895                Ext.a11y.FocusFrame.frame(this.relayTo.frameEl);
896            }
897            if (!this.noFrame) {
898                Ext.a11y.RelayFrame.frame(this.frameEl);
899            }
900        }
901        else {
902            if (!this.noFrame) {
903                Ext.a11y.FocusFrame.frame(this.frameEl);
904            }
905        }
906       
907        this.fireEvent('focus', e, t, this);
908    },
909   
910    onBlur: function(e, t){
911        if (this.relayTo) {
912            this.relayTo.el.removeClass('x-a11y-focused-relay');
913            Ext.a11y.RelayFrame.unframe();
914        }
915        this.el.removeClass('x-a11y-focused');
916        Ext.a11y.FocusFrame.unframe();
917        this.fireEvent('blur', e, t, this);
918    },
919   
920    destroy: function(){
921        this.el.un('keydown', this.onKeyDown);
922        this.el.un('focus', this.onFocus);
923        this.el.un('blur', this.onBlur);
924        this.el.removeClass('x-a11y-focusable');
925        this.el.removeClass('x-a11y-focused');
926        if (this.relayTo) {
927            this.relayTo.el.removeClass('x-a11y-focused-relay');
928        }
929    }
930});
931
932Ext.a11y.FocusItem = Ext.extend(Object, {
933    constructor: function(el, enableTabbing){
934        Ext.a11y.FocusItem.superclass.constructor.call(this);
935       
936        this.el = Ext.get(el);
937        this.fi = new Ext.a11y.Focusable(el);
938        this.fi.setComponent(this);
939       
940        this.fi.on('tab', this.onTab, this);
941       
942        this.enableTabbing = enableTabbing === true ? true : false;
943    },
944   
945    getEnterItem: function(){
946        if (this.enableTabbing) {
947            var items = this.getFocusItems();
948            if (items && items.length) {
949                return items[0];
950            }
951        }
952    },
953   
954    getFocusItems: function(){
955        if (this.enableTabbing) {
956            return this.el.query('a, button, input, select');
957        }
958        return null;
959    },
960   
961    onTab: function(e, t){
962        var items = this.getFocusItems(), i;
963       
964        if (items && items.length && (i = items.indexOf(t)) !== -1) {
965            if (e.shiftKey && i > 0) {
966                e.stopEvent();
967                items[i - 1].focus();
968                Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);
969                return;
970            }
971            else 
972                if (!e.shiftKey && i < items.length - 1) {
973                    e.stopEvent();
974                    items[i + 1].focus();
975                    Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);
976                    return;
977                }
978        }
979    },
980   
981    focus: function(){
982        if (this.enableTabbing) {
983            var items = this.getFocusItems();
984            if (items && items.length) {
985                items[0].focus();
986                Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);
987                return;
988            }
989        }
990        this.fi.focus();
991    },
992   
993    blur: function(){
994        this.fi.blur();
995    }
996});
997
998Ext.a11y.FocusMgr = function(){
999    var all = new Ext.util.MixedCollection();
1000   
1001    return {
1002        register: function(f){
1003            all.add(f.el && Ext.id(f.el), f);
1004        },
1005       
1006        unregister: function(f){
1007            all.remove(f);
1008        },
1009       
1010        get: function(el, noCreate){
1011            return all.get(Ext.id(el)) || (noCreate ? false : new Ext.a11y.Focusable(el));
1012        },
1013       
1014        all: all
1015    }
1016}();
1017
1018Ext.a11y.Focusable.SpecialKeys = {};
1019Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.LEFT] = 'left';
1020Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.RIGHT] = 'right';
1021Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.DOWN] = 'down';
1022Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.UP] = 'up';
1023Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ESC] = 'esc';
1024Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ENTER] = 'enter';
1025Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.SPACE] = 'space';
1026Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.TAB] = 'tab';
1027
1028// we use the new observeClass method to fire our new initFocus method on components
1029Ext.util.Observable.observeClass(Ext.Component);
1030Ext.Component.on('render', function(cmp){
1031    cmp.initFocus();
1032    cmp.initARIA();
1033});
1034Ext.override(Ext.Component, {
1035    initFocus: Ext.emptyFn,
1036    initARIA: Ext.emptyFn
1037});
1038
1039Ext.override(Ext.Container, {
1040    isFocusable: true,
1041    noFocus: false,
1042   
1043    // private
1044    initFocus: function(){
1045        if (!this.fi && !this.noFocus) {
1046            this.fi = new Ext.a11y.Focusable(this);
1047        }
1048        this.mon(this.fi, {
1049            focus: this.onFocus,
1050            blur: this.onBlur,
1051            tab: this.onTab,
1052            enter: this.onEnter,
1053            esc: this.onEsc,
1054            scope: this
1055        });
1056       
1057        if (this.hidden) {
1058            this.isFocusable = false;
1059        }
1060       
1061        this.on('show', function(){
1062            this.isFocusable = true;
1063        }, this);
1064        this.on('hide', function(){
1065            this.isFocusable = false;
1066        }, this);
1067    },
1068   
1069    focus: function(){
1070        this.fi.focus();
1071    },
1072   
1073    blur: function(){
1074        this.fi.blur();
1075    },
1076   
1077    enter: function(){
1078        var eitem = this.getEnterItem();
1079        if (eitem) {
1080            eitem.focus();
1081        }
1082    },
1083   
1084    onFocus: Ext.emptyFn,
1085    onBlur: Ext.emptyFn,
1086   
1087    onTab: function(e, t, tf){
1088        var rf = tf.relayTo || tf;
1089        if (rf.component && rf.component !== this) {
1090            e.stopEvent();
1091            var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);
1092            item.focus();
1093        }
1094    },
1095   
1096    onEnter: function(e, t, tf){
1097        // check to see if enter is pressed while "on" the panel
1098        if (tf.component && tf.component === this) {
1099            e.stopEvent();
1100            this.enter();
1101        }
1102        e.stopPropagation();
1103    },
1104   
1105    onEsc: function(e, t){
1106        e.preventDefault();
1107       
1108        // check to see if esc is pressed while "inside" the panel
1109        // or while "on" the panel
1110        if (t === this.el.dom) {
1111            // "on" the panel, check if this panel has an owner panel and focus that
1112            // we dont stop the event in this case so that this same check will be
1113            // done for this ownerCt
1114            if (this.ownerCt) {
1115                this.ownerCt.focus();
1116            }
1117        }
1118        else {
1119            // we were inside the panel when esc was pressed,
1120            // so go back "on" the panel
1121            if (this.ownerCt && this.ownerCt.isFocusable) {
1122                var si = this.ownerCt.getFocusItems();
1123               
1124                if (si && si.getCount() > 1) {
1125                    e.stopEvent();
1126                }
1127            }
1128            this.focus();
1129        }
1130    },
1131   
1132    getFocusItems: function(){
1133        return this.items &&
1134        this.items.filterBy(function(o){
1135            return o.isFocusable;
1136        }) ||
1137        null;
1138    },
1139   
1140    getEnterItem: function(){
1141        var ci = this.getFocusItems(), length = ci ? ci.getCount() : 0;
1142       
1143        if (length === 1) {
1144            return ci.first().getEnterItem && ci.first().getEnterItem() || ci.first();
1145        }
1146        else 
1147            if (length > 1) {
1148                return ci.first();
1149            }
1150    },
1151   
1152    getNextFocus: function(current){
1153        var items = this.getFocusItems(), next = current, i = items.indexOf(current), length = items.getCount();
1154       
1155        if (i === length - 1) {
1156            next = items.first();
1157        }
1158        else {
1159            next = items.get(i + 1);
1160        }
1161        return next;
1162    },
1163   
1164    getPreviousFocus: function(current){
1165        var items = this.getFocusItems(), prev = current, i = items.indexOf(current), length = items.getCount();
1166       
1167        if (i === 0) {
1168            prev = items.last();
1169        }
1170        else {
1171            prev = items.get(i - 1);
1172        }
1173        return prev;
1174    },
1175   
1176    getFocusable : function() {
1177        return this.fi;
1178    }
1179});
1180
1181Ext.override(Ext.Panel, {
1182    /**
1183     * @cfg {Boolean} enableTabbing <tt>true</tt> to enable tabbing. Default is <tt>false</tt>.
1184     */       
1185    getFocusItems: function(){
1186        // items gets all the items inside the body
1187        var items = Ext.Panel.superclass.getFocusItems.call(this), bodyFocus = null;
1188       
1189        if (!items) {
1190            items = new Ext.util.MixedCollection();
1191            this.bodyFocus = this.bodyFocus || new Ext.a11y.FocusItem(this.body, this.enableTabbing);
1192            items.add('body', this.bodyFocus);
1193        }
1194        // but panels can also have tbar, bbar, fbar
1195        if (this.tbar && this.topToolbar) {
1196            items.insert(0, this.topToolbar);
1197        }
1198        if (this.bbar && this.bottomToolbar) {
1199            items.add(this.bottomToolbar);
1200        }
1201        if (this.fbar) {
1202            items.add(this.fbar);
1203        }
1204       
1205        return items;
1206    }
1207});
1208
1209Ext.override(Ext.TabPanel, {
1210    // private
1211    initFocus: function(){
1212        Ext.TabPanel.superclass.initFocus.call(this);
1213        this.mon(this.fi, {
1214            left: this.onLeft,
1215            right: this.onRight,
1216            scope: this
1217        });
1218    },
1219   
1220    onLeft: function(e){
1221        if (!this.activeTab) {
1222            return;
1223        }
1224        e.stopEvent();
1225        var prev = this.items.itemAt(this.items.indexOf(this.activeTab) - 1);
1226        if (prev) {
1227            this.setActiveTab(prev);
1228        }
1229        return false;
1230    },
1231   
1232    onRight: function(e){
1233        if (!this.activeTab) {
1234            return;
1235        }
1236        e.stopEvent();
1237        var next = this.items.itemAt(this.items.indexOf(this.activeTab) + 1);
1238        if (next) {
1239            this.setActiveTab(next);
1240        }
1241        return false;
1242    }
1243});
1244
1245Ext.override(Ext.tree.TreeNodeUI, {
1246    // private
1247    focus: function(){
1248        this.node.getOwnerTree().bodyFocus.focus();
1249    }
1250});
1251
1252Ext.override(Ext.tree.TreePanel, {
1253    // private
1254    afterRender : function(){
1255        Ext.tree.TreePanel.superclass.afterRender.call(this);
1256        this.root.render();
1257        if(!this.rootVisible){
1258            this.root.renderChildren();
1259        }
1260        this.bodyFocus = new Ext.a11y.FocusItem(this.body.down('.x-tree-root-ct'));
1261        this.bodyFocus.fi.setFrameEl(this.body);
1262    } 
1263});
1264
1265Ext.override(Ext.grid.GridPanel, {
1266    initFocus: function(){
1267        Ext.grid.GridPanel.superclass.initFocus.call(this);
1268        this.bodyFocus = new Ext.a11y.FocusItem(this.view.focusEl);
1269        this.bodyFocus.fi.setFrameEl(this.body);
1270    }
1271});
1272
1273Ext.override(Ext.Button, {
1274    isFocusable: true,
1275    noFocus: false,
1276   
1277    initFocus: function(){
1278        Ext.Button.superclass.initFocus.call(this);
1279        this.fi = this.fi || new Ext.a11y.Focusable(this.btnEl, null, null, this.el);
1280        this.fi.setComponent(this);
1281       
1282        this.mon(this.fi, {
1283            focus: this.onFocus,
1284            blur: this.onBlur,
1285            scope: this
1286        });
1287       
1288        if (this.menu) {
1289            this.mon(this.fi, 'down', this.showMenu, this);
1290            this.on('menuhide', this.focus, this);
1291        }
1292       
1293        if (this.hidden) {
1294            this.isFocusable = false;
1295        }
1296       
1297        this.on('show', function(){
1298            this.isFocusable = true;
1299        }, this);
1300        this.on('hide', function(){
1301            this.isFocusable = false;
1302        }, this);
1303    },
1304   
1305    focus: function(){
1306        this.fi.focus();
1307    },
1308   
1309    blur: function(){
1310        this.fi.blur();
1311    },
1312   
1313    onFocus: function(){
1314        if (!this.disabled) {
1315            this.el.addClass("x-btn-focus");
1316        }
1317    },
1318   
1319    onBlur: function(){
1320        this.el.removeClass("x-btn-focus");
1321    }
1322});
1323
1324Ext.override(Ext.Toolbar, {
1325    initFocus: function(){
1326        Ext.Toolbar.superclass.initFocus.call(this);
1327        this.mon(this.fi, {
1328            left: this.onLeft,
1329            right: this.onRight,
1330            scope: this
1331        });
1332       
1333        this.on('focus', this.onButtonFocus, this, {
1334            stopEvent: true
1335        });
1336    },
1337   
1338    addItem: function(item){
1339        Ext.Toolbar.superclass.add.apply(this, arguments);
1340        if (item.rendered && item.fi !== undefined) {
1341            item.fi.setRelayTo(this.el);
1342            this.relayEvents(item.fi, ['focus']);
1343        }
1344        else {
1345            item.on('render', function(){
1346                if (item.fi !== undefined) {
1347                    item.fi.setRelayTo(this.el);
1348                    this.relayEvents(item.fi, ['focus']);
1349                }
1350            }, this, {
1351                single: true
1352            });
1353        }
1354        return item;
1355    },
1356   
1357    onFocus: function(){
1358        var items = this.getFocusItems();
1359        if (items && items.getCount() > 0) {
1360            if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {
1361                this.lastFocus.focus();
1362            }
1363            else {
1364                items.first().focus();
1365            }
1366        }
1367    },
1368   
1369    onButtonFocus: function(e, t, tf){
1370        this.lastFocus = tf.component || null;
1371    },
1372   
1373    onLeft: function(e, t, tf){
1374        e.stopEvent();
1375        this.getPreviousFocus(tf.component).focus();
1376    },
1377   
1378    onRight: function(e, t, tf){
1379        e.stopEvent();
1380        this.getNextFocus(tf.component).focus();
1381    },
1382   
1383    getEnterItem: Ext.emptyFn,
1384    onTab: Ext.emptyFn,
1385    onEsc: Ext.emptyFn
1386});
1387
1388Ext.override(Ext.menu.BaseItem, {
1389    initFocus: function(){
1390        this.fi = new Ext.a11y.Focusable(this, this.parentMenu && this.parentMenu.el || null, true);
1391    }
1392});
1393
1394Ext.override(Ext.menu.Menu, {
1395    initFocus: function(){
1396        this.fi = new Ext.a11y.Focusable(this);
1397        this.focusEl = this.fi;
1398    }
1399});
1400
1401Ext.a11y.WindowMgr = new Ext.WindowGroup();
1402
1403Ext.apply(Ext.WindowMgr, {
1404    bringToFront: function(win){
1405        Ext.a11y.WindowMgr.bringToFront.call(this, win);
1406        if (win.modal) {
1407            win.enter();
1408        }
1409        else {
1410            win.focus();
1411        }
1412    }
1413});
1414
1415Ext.override(Ext.Window, {
1416    initFocus: function(){
1417        Ext.Window.superclass.initFocus.call(this);
1418        this.on('beforehide', function(){
1419            Ext.a11y.RelayFrame.unframe();
1420            Ext.a11y.FocusFrame.unframe();
1421        });
1422    }
1423});
1424
1425Ext.override(Ext.form.Field, {
1426    isFocusable: true,
1427    noFocus: false,
1428   
1429    initFocus: function(){
1430        this.fi = this.fi || new Ext.a11y.Focusable(this, null, true);
1431       
1432        Ext.form.Field.superclass.initFocus.call(this);
1433       
1434        if (this.hidden) {
1435            this.isFocusable = false;
1436        }
1437       
1438        this.on('show', function(){
1439            this.isFocusable = true;
1440        }, this);
1441        this.on('hide', function(){
1442            this.isFocusable = false;
1443        }, this);
1444    }
1445});
1446
1447Ext.override(Ext.FormPanel, {
1448    initFocus: function(){
1449        Ext.FormPanel.superclass.initFocus.call(this);
1450        this.on('focus', this.onFieldFocus, this, {
1451            stopEvent: true
1452        });
1453    },
1454   
1455    // private
1456    createForm: function(){
1457        delete this.initialConfig.listeners;
1458        var form = new Ext.form.BasicForm(null, this.initialConfig);
1459        form.afterMethod('add', this.formItemAdd, this);
1460        return form;
1461    },
1462   
1463    formItemAdd: function(item){
1464        item.on('render', function(field){
1465            field.fi.setRelayTo(this.el);
1466            this.relayEvents(field.fi, ['focus']);
1467        }, this, {
1468            single: true
1469        });
1470    },
1471   
1472    onFocus: function(){
1473        var items = this.getFocusItems();
1474        if (items && items.getCount() > 0) {
1475            if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {
1476                this.lastFocus.focus();
1477            }
1478            else {
1479                items.first().focus();
1480            }
1481        }
1482    },
1483   
1484    onFieldFocus: function(e, t, tf){
1485        this.lastFocus = tf.component || null;
1486    },
1487   
1488    onTab: function(e, t, tf){
1489        if (tf.relayTo.component === this) {
1490            var item = e.shiftKey ? this.getPreviousFocus(tf.component) : this.getNextFocus(tf.component);
1491           
1492            if (item) {
1493                ev.stopEvent();
1494                item.focus();
1495                return;
1496            }
1497        }
1498        Ext.FormPanel.superclass.onTab.apply(this, arguments);
1499    },
1500   
1501    getNextFocus: function(current){
1502        var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();
1503       
1504        return (i < length - 1) ? items.get(i + 1) : false;
1505    },
1506   
1507    getPreviousFocus: function(current){
1508        var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();
1509       
1510        return (i > 0) ? items.get(i - 1) : false;
1511    }
1512});
1513
1514Ext.override(Ext.Viewport, {
1515    initFocus: function(){
1516        Ext.Viewport.superclass.initFocus.apply(this);
1517        this.mon(Ext.get(document), 'focus', this.focus, this);
1518        this.mon(Ext.get(document), 'blur', this.blur, this);
1519        this.fi.setNoFrame(true);
1520    },
1521   
1522    onTab: function(e, t, tf, f){
1523        e.stopEvent();
1524       
1525        if (tf === f) {
1526            items = this.getFocusItems();
1527            if (items && items.getCount() > 0) {
1528                items.first().focus();
1529            }
1530        }
1531        else {
1532            var rf = tf.relayTo || tf;
1533            var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);
1534            item.focus();
1535        }
1536    }
1537});
1538   
1539})();/**
1540 * @class Ext.ux.GMapPanel
1541 * @extends Ext.Panel
1542 * @author Shea Frederick
1543 */
1544Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {
1545    initComponent : function(){
1546       
1547        var defConfig = {
1548            plain: true,
1549            zoomLevel: 3,
1550            yaw: 180,
1551            pitch: 0,
1552            zoom: 0,
1553            gmapType: 'map',
1554            border: false
1555        };
1556       
1557        Ext.applyIf(this,defConfig);
1558       
1559        Ext.ux.GMapPanel.superclass.initComponent.call(this);       
1560
1561    },
1562    afterRender : function(){
1563       
1564        var wh = this.ownerCt.getSize();
1565        Ext.applyIf(this, wh);
1566       
1567        Ext.ux.GMapPanel.superclass.afterRender.call(this);   
1568       
1569        if (this.gmapType === 'map'){
1570            this.gmap = new GMap2(this.body.dom);
1571        }
1572       
1573        if (this.gmapType === 'panorama'){
1574            this.gmap = new GStreetviewPanorama(this.body.dom);
1575        }
1576       
1577        if (typeof this.addControl == 'object' && this.gmapType === 'map') {
1578            this.gmap.addControl(this.addControl);
1579        }
1580       
1581        if (typeof this.setCenter === 'object') {
1582            if (typeof this.setCenter.geoCodeAddr === 'string'){
1583                this.geoCodeLookup(this.setCenter.geoCodeAddr);
1584            }else{
1585                if (this.gmapType === 'map'){
1586                    var point = new GLatLng(this.setCenter.lat,this.setCenter.lng);
1587                    this.gmap.setCenter(point, this.zoomLevel);   
1588                }
1589                if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){
1590                    this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);
1591                }
1592            }
1593            if (this.gmapType === 'panorama'){
1594                this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});
1595            }
1596        }
1597
1598        GEvent.bind(this.gmap, 'load', this, function(){
1599            this.onMapReady();
1600        });
1601
1602    },
1603    onMapReady : function(){
1604        this.addMarkers(this.markers);
1605        this.addMapControls();
1606        this.addOptions(); 
1607    },
1608    onResize : function(w, h){
1609
1610        if (typeof this.getMap() == 'object') {
1611            this.gmap.checkResize();
1612        }
1613       
1614        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);
1615
1616    },
1617    setSize : function(width, height, animate){
1618       
1619        if (typeof this.getMap() == 'object') {
1620            this.gmap.checkResize();
1621        }
1622       
1623        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
1624       
1625    },
1626    getMap : function(){
1627       
1628        return this.gmap;
1629       
1630    },
1631    getCenter : function(){
1632       
1633        return this.getMap().getCenter();
1634       
1635    },
1636    getCenterLatLng : function(){
1637       
1638        var ll = this.getCenter();
1639        return {lat: ll.lat(), lng: ll.lng()};
1640       
1641    },
1642    addMarkers : function(markers) {
1643       
1644        if (Ext.isArray(markers)){
1645            for (var i = 0; i < markers.length; i++) {
1646                var mkr_point = new GLatLng(markers[i].lat,markers[i].lng);
1647                this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners);
1648            }
1649        }
1650       
1651    },
1652    addMarker : function(point, marker, clear, center, listeners){
1653       
1654        Ext.applyIf(marker,G_DEFAULT_ICON);
1655
1656        if (clear === true){
1657            this.getMap().clearOverlays();
1658        }
1659        if (center === true) {
1660            this.getMap().setCenter(point, this.zoomLevel);
1661        }
1662
1663        var mark = new GMarker(point,marker);
1664        if (typeof listeners === 'object'){
1665            for (evt in listeners) {
1666                GEvent.bind(mark, evt, this, listeners[evt]);
1667            }
1668        }
1669        this.getMap().addOverlay(mark);
1670
1671    },
1672    addMapControls : function(){
1673       
1674        if (this.gmapType === 'map') {
1675            if (Ext.isArray(this.mapControls)) {
1676                for(i=0;i<this.mapControls.length;i++){
1677                    this.addMapControl(this.mapControls[i]);
1678                }
1679            }else if(typeof this.mapControls === 'string'){
1680                this.addMapControl(this.mapControls);
1681            }else if(typeof this.mapControls === 'object'){
1682                this.getMap().addControl(this.mapControls);
1683            }
1684        }
1685       
1686    },
1687    addMapControl : function(mc){
1688       
1689        var mcf = window[mc];
1690        if (typeof mcf === 'function') {
1691            this.getMap().addControl(new mcf());
1692        }   
1693       
1694    },
1695    addOptions : function(){
1696       
1697        if (Ext.isArray(this.mapConfOpts)) {
1698            var mc;
1699            for(i=0;i<this.mapConfOpts.length;i++){
1700                this.addOption(this.mapConfOpts[i]);
1701            }
1702        }else if(typeof this.mapConfOpts === 'string'){
1703            this.addOption(this.mapConfOpts);
1704        }       
1705       
1706    },
1707    addOption : function(mc){
1708       
1709        var mcf = this.getMap()[mc];
1710        if (typeof mcf === 'function') {
1711            this.getMap()[mc]();
1712        }   
1713       
1714    },
1715    geoCodeLookup : function(addr) {
1716       
1717        this.geocoder = new GClientGeocoder();
1718        this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));
1719       
1720    },
1721    addAddressToMap : function(response) {
1722       
1723        if (!response || response.Status.code != 200) {
1724            Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');
1725        }else{
1726            place = response.Placemark[0];
1727            addressinfo = place.AddressDetails;
1728            accuracy = addressinfo.Accuracy;
1729            if (accuracy === 0) {
1730                Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');
1731            }else{
1732                if (accuracy < 7) {
1733                    Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');
1734                }else{
1735                    point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
1736                    if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){
1737                        this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners);
1738                    }
1739                }
1740            }
1741        }
1742       
1743    }
1744 
1745});
1746
1747Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.ns('Ext.ux.grid');
1748
1749/**
1750 * @class Ext.ux.grid.GroupSummary
1751 * @extends Ext.util.Observable
1752 * A GridPanel plugin that enables dynamic column calculations and a dynamically
1753 * updated grouped summary row.
1754 */
1755Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {
1756    /**
1757     * @cfg {Function} summaryRenderer Renderer example:<pre><code>
1758summaryRenderer: function(v, params, data){
1759    return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
1760},
1761     * </code></pre>
1762     */
1763    /**
1764     * @cfg {String} summaryType (Optional) The type of
1765     * calculation to be used for the column.  For options available see
1766     * {@link #Calculations}.
1767     */
1768
1769    constructor : function(config){
1770        Ext.apply(this, config);
1771        Ext.ux.grid.GroupSummary.superclass.constructor.call(this);
1772    },
1773    init : function(grid){
1774        this.grid = grid;
1775        this.cm = grid.getColumnModel();
1776        this.view = grid.getView();
1777
1778        var v = this.view;
1779        v.doGroupEnd = this.doGroupEnd.createDelegate(this);
1780
1781        v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
1782        v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
1783        v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);
1784        v.afterMethod('onUpdate', this.doUpdate, this);
1785        v.afterMethod('onRemove', this.doRemove, this);
1786
1787        if(!this.rowTpl){
1788            this.rowTpl = new Ext.Template(
1789                '<div class="x-grid3-summary-row" style="{tstyle}">',
1790                '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
1791                    '<tbody><tr>{cells}</tr></tbody>',
1792                '</table></div>'
1793            );
1794            this.rowTpl.disableFormats = true;
1795        }
1796        this.rowTpl.compile();
1797
1798        if(!this.cellTpl){
1799            this.cellTpl = new Ext.Template(
1800                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
1801                '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',
1802                "</td>"
1803            );
1804            this.cellTpl.disableFormats = true;
1805        }
1806        this.cellTpl.compile();
1807    },
1808
1809    /**
1810     * Toggle the display of the summary row on/off
1811     * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.
1812     */
1813    toggleSummaries : function(visible){
1814        var el = this.grid.getGridEl();
1815        if(el){
1816            if(visible === undefined){
1817                visible = el.hasClass('x-grid-hide-summary');
1818            }
1819            el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');
1820        }
1821    },
1822
1823    renderSummary : function(o, cs){
1824        cs = cs || this.view.getColumnData();
1825        var cfg = this.cm.config;
1826
1827        var buf = [], c, p = {}, cf, last = cs.length-1;
1828        for(var i = 0, len = cs.length; i < len; i++){
1829            c = cs[i];
1830            cf = cfg[i];
1831            p.id = c.id;
1832            p.style = c.style;
1833            p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
1834            if(cf.summaryType || cf.summaryRenderer){
1835                p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
1836            }else{
1837                p.value = '';
1838            }
1839            if(p.value == undefined || p.value === "") p.value = "&#160;";
1840            buf[buf.length] = this.cellTpl.apply(p);
1841        }
1842
1843        return this.rowTpl.apply({
1844            tstyle: 'width:'+this.view.getTotalWidth()+';',
1845            cells: buf.join('')
1846        });
1847    },
1848
1849    /**
1850     * @private
1851     * @param {Object} rs
1852     * @param {Object} cs
1853     */
1854    calculate : function(rs, cs){
1855        var data = {}, r, c, cfg = this.cm.config, cf;
1856        for(var j = 0, jlen = rs.length; j < jlen; j++){
1857            r = rs[j];
1858            for(var i = 0, len = cs.length; i < len; i++){
1859                c = cs[i];
1860                cf = cfg[i];
1861                if(cf.summaryType){
1862                    data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);
1863                }
1864            }
1865        }
1866        return data;
1867    },
1868
1869    doGroupEnd : function(buf, g, cs, ds, colCount){
1870        var data = this.calculate(g.rs, cs);
1871        buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');
1872    },
1873
1874    doWidth : function(col, w, tw){
1875        var gs = this.view.getGroups(), s;
1876        for(var i = 0, len = gs.length; i < len; i++){
1877            s = gs[i].childNodes[2];
1878            s.style.width = tw;
1879            s.firstChild.style.width = tw;
1880            s.firstChild.rows[0].childNodes[col].style.width = w;
1881        }
1882    },
1883
1884    doAllWidths : function(ws, tw){
1885        var gs = this.view.getGroups(), s, cells, wlen = ws.length;
1886        for(var i = 0, len = gs.length; i < len; i++){
1887            s = gs[i].childNodes[2];
1888            s.style.width = tw;
1889            s.firstChild.style.width = tw;
1890            cells = s.firstChild.rows[0].childNodes;
1891            for(var j = 0; j < wlen; j++){
1892                cells[j].style.width = ws[j];
1893            }
1894        }
1895    },
1896
1897    doHidden : function(col, hidden, tw){
1898        var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';
1899        for(var i = 0, len = gs.length; i < len; i++){
1900            s = gs[i].childNodes[2];
1901            s.style.width = tw;
1902            s.firstChild.style.width = tw;
1903            s.firstChild.rows[0].childNodes[col].style.display = display;
1904        }
1905    },
1906
1907    // Note: requires that all (or the first) record in the
1908    // group share the same group value. Returns false if the group
1909    // could not be found.
1910    refreshSummary : function(groupValue){
1911        return this.refreshSummaryById(this.view.getGroupId(groupValue));
1912    },
1913
1914    getSummaryNode : function(gid){
1915        var g = Ext.fly(gid, '_gsummary');
1916        if(g){
1917            return g.down('.x-grid3-summary-row', true);
1918        }
1919        return null;
1920    },
1921
1922    refreshSummaryById : function(gid){
1923        var g = document.getElementById(gid);
1924        if(!g){
1925            return false;
1926        }
1927        var rs = [];
1928        this.grid.store.each(function(r){
1929            if(r._groupId == gid){
1930                rs[rs.length] = r;
1931            }
1932        });
1933        var cs = this.view.getColumnData();
1934        var data = this.calculate(rs, cs);
1935        var markup = this.renderSummary({data: data}, cs);
1936
1937        var existing = this.getSummaryNode(gid);
1938        if(existing){
1939            g.removeChild(existing);
1940        }
1941        Ext.DomHelper.append(g, markup);
1942        return true;
1943    },
1944
1945    doUpdate : function(ds, record){
1946        this.refreshSummaryById(record._groupId);
1947    },
1948
1949    doRemove : function(ds, record, index, isUpdate){
1950        if(!isUpdate){
1951            this.refreshSummaryById(record._groupId);
1952        }
1953    },
1954
1955    /**
1956     * Show a message in the summary row.
1957     * <pre><code>
1958grid.on('afteredit', function(){
1959    var groupValue = 'Ext Forms: Field Anchoring';
1960    summary.showSummaryMsg(groupValue, 'Updating Summary...');
1961});
1962     * </code></pre>
1963     * @param {String} groupValue
1964     * @param {String} msg Text to use as innerHTML for the summary row.
1965     */
1966    showSummaryMsg : function(groupValue, msg){
1967        var gid = this.view.getGroupId(groupValue);
1968        var node = this.getSummaryNode(gid);
1969        if(node){
1970            node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';
1971        }
1972    }
1973});
1974
1975//backwards compat
1976Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;
1977
1978
1979/**
1980 * Calculation types for summary row:</p><div class="mdetail-params"><ul>
1981 * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>
1982 * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>
1983 * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>
1984 * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>
1985 * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>
1986 * </ul></div>
1987 * <p>Custom calculations may be implemented.  An example of
1988 * custom <code>summaryType=totalCost</code>:</p><pre><code>
1989// define a custom summary function
1990Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
1991    return v + (record.data.estimate * record.data.rate);
1992};
1993 * </code></pre>
1994 * @property Calculations
1995 */
1996
1997Ext.ux.grid.GroupSummary.Calculations = {
1998    'sum' : function(v, record, field){
1999        return v + (record.data[field]||0);
2000    },
2001
2002    'count' : function(v, record, field, data){
2003        return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
2004    },
2005
2006    'max' : function(v, record, field, data){
2007        var v = record.data[field];
2008        var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];
2009        return v > max ? (data[field+'max'] = v) : max;
2010    },
2011
2012    'min' : function(v, record, field, data){
2013        var v = record.data[field];
2014        var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];
2015        return v < min ? (data[field+'min'] = v) : min;
2016    },
2017
2018    'average' : function(v, record, field, data){
2019        var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
2020        var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));
2021        return t === 0 ? 0 : t / c;
2022    }
2023};
2024Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;
2025
2026/**
2027 * @class Ext.ux.grid.HybridSummary
2028 * @extends Ext.ux.grid.GroupSummary
2029 * Adds capability to specify the summary data for the group via json as illustrated here:
2030 * <pre><code>
2031{
2032    data: [
2033        {
2034            projectId: 100,     project: 'House',
2035            taskId:    112, description: 'Paint',
2036            estimate:    6,        rate:     150,
2037            due:'06/24/2007'
2038        },
2039        ...
2040    ],
2041
2042    summaryData: {
2043        'House': {
2044            description: 14, estimate: 9,
2045                   rate: 99, due: new Date(2009, 6, 29),
2046                   cost: 999
2047        }
2048    }
2049}
2050 * </code></pre>
2051 *
2052 */
2053Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {
2054    /**
2055     * @private
2056     * @param {Object} rs
2057     * @param {Object} cs
2058     */
2059    calculate : function(rs, cs){
2060        var gcol = this.view.getGroupField();
2061        var gvalue = rs[0].data[gcol];
2062        var gdata = this.getSummaryData(gvalue);
2063        return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);
2064    },
2065
2066    /**
2067     * <pre><code>
2068grid.on('afteredit', function(){
2069    var groupValue = 'Ext Forms: Field Anchoring';
2070    summary.showSummaryMsg(groupValue, 'Updating Summary...');
2071    setTimeout(function(){ // simulate server call
2072        // HybridSummary class implements updateSummaryData
2073        summary.updateSummaryData(groupValue,
2074            // create data object based on configured dataIndex
2075            {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});
2076    }, 2000);
2077});
2078     * </code></pre>
2079     * @param {String} groupValue
2080     * @param {Object} data data object
2081     * @param {Boolean} skipRefresh (Optional) Defaults to false
2082     */
2083    updateSummaryData : function(groupValue, data, skipRefresh){
2084        var json = this.grid.store.reader.jsonData;
2085        if(!json.summaryData){
2086            json.summaryData = {};
2087        }
2088        json.summaryData[groupValue] = data;
2089        if(!skipRefresh){
2090            this.refreshSummary(groupValue);
2091        }
2092    },
2093
2094    /**
2095     * Returns the summaryData for the specified groupValue or null.
2096     * @param {String} groupValue
2097     * @return {Object} summaryData
2098     */
2099    getSummaryData : function(groupValue){
2100        var json = this.grid.store.reader.jsonData;
2101        if(json && json.summaryData){
2102            return json.summaryData[groupValue];
2103        }
2104        return null;
2105    }
2106});
2107
2108//backwards compat
2109Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;
2110Ext.ux.GroupTab = Ext.extend(Ext.Container, {
2111    mainItem: 0,
2112   
2113    expanded: true,
2114   
2115    deferredRender: true,
2116   
2117    activeTab: null,
2118   
2119    idDelimiter: '__',
2120   
2121    headerAsText: false,
2122   
2123    frame: false,
2124   
2125    hideBorders: true,
2126   
2127    initComponent: function(config){
2128        Ext.apply(this, config);
2129        this.frame = false;
2130       
2131        Ext.ux.GroupTab.superclass.initComponent.call(this);
2132       
2133        this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');
2134       
2135        this.setLayout(new Ext.layout.CardLayout({
2136            deferredRender: this.deferredRender
2137        }));
2138       
2139        if (!this.stack) {
2140            this.stack = Ext.TabPanel.AccessStack();
2141        }
2142       
2143        this.initItems();
2144       
2145        this.on('beforerender', function(){
2146            this.groupEl = this.ownerCt.getGroupEl(this);
2147        }, this);
2148       
2149        this.on('add', this.onAdd, this, {
2150            target: this
2151        });
2152        this.on('remove', this.onRemove, this, {
2153            target: this
2154        });
2155       
2156        if (this.mainItem !== undefined) {
2157            var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);
2158            delete this.mainItem;
2159            this.setMainItem(item);
2160        }
2161    },
2162   
2163    /**
2164     * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which
2165     * can return false to cancel the tab change.
2166     * @param {String/Panel} tab The id or tab Panel to activate
2167     */
2168    setActiveTab : function(item){
2169        item = this.getComponent(item);
2170        if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){
2171            return;
2172        }
2173        if(!this.rendered){
2174            this.activeTab = item;
2175            return;
2176        }
2177        if(this.activeTab != item){
2178            if(this.activeTab && this.activeTab != this.mainItem){
2179                var oldEl = this.getTabEl(this.activeTab);
2180                if(oldEl){
2181                    Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');
2182                }
2183                this.activeTab.fireEvent('deactivate', this.activeTab);
2184            }
2185            var el = this.getTabEl(item);
2186            Ext.fly(el).addClass('x-grouptabs-strip-active');
2187            this.activeTab = item;
2188            this.stack.add(item);
2189
2190            this.layout.setActiveItem(item);
2191            if(this.layoutOnTabChange && item.doLayout){
2192                item.doLayout();
2193            }
2194            if(this.scrolling){
2195                this.scrollToTab(item, this.animScroll);
2196            }
2197
2198            item.fireEvent('activate', item);
2199            this.fireEvent('tabchange', this, item);
2200        }
2201    },
2202   
2203    getTabEl: function(item){
2204        if (item == this.mainItem) {
2205            return this.groupEl;
2206        }
2207        return Ext.TabPanel.prototype.getTabEl.call(this, item);
2208    },
2209   
2210    onRender: function(ct, position){
2211        Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);
2212       
2213        this.strip = Ext.fly(this.groupEl).createChild({
2214            tag: 'ul',
2215            cls: 'x-grouptabs-sub'
2216        });
2217
2218        this.tooltip = new Ext.ToolTip({
2219           target: this.groupEl,
2220           delegate: 'a.x-grouptabs-text',
2221           trackMouse: true,
2222           renderTo: document.body,
2223           listeners: {
2224               beforeshow: function(tip) {
2225                   var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)
2226                       ? this.mainItem
2227                       : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);
2228
2229                   if(!item.tabTip) {
2230                       return false;
2231                   }
2232                   tip.body.dom.innerHTML = item.tabTip;
2233               },
2234               scope: this
2235           }
2236        });
2237               
2238        if (!this.itemTpl) {
2239            var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');
2240            tt.disableFormats = true;
2241            tt.compile();
2242            Ext.ux.GroupTab.prototype.itemTpl = tt;
2243        }
2244       
2245        this.items.each(this.initTab, this);
2246    },
2247   
2248    afterRender: function(){
2249        Ext.ux.GroupTab.superclass.afterRender.call(this);
2250       
2251        if (this.activeTab !== undefined) {
2252            var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);
2253            delete this.activeTab;
2254            this.setActiveTab(item);
2255        }
2256    },
2257   
2258    // private
2259    initTab: function(item, index){
2260        var before = this.strip.dom.childNodes[index];
2261        var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);
2262       
2263        if (item === this.mainItem) {
2264            item.tabEl = this.groupEl;
2265            p.cls += ' x-grouptabs-main-item';
2266        }
2267       
2268        var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);
2269       
2270        item.tabEl = item.tabEl || el;
2271               
2272        item.on('disable', this.onItemDisabled, this);
2273        item.on('enable', this.onItemEnabled, this);
2274        item.on('titlechange', this.onItemTitleChanged, this);
2275        item.on('iconchange', this.onItemIconChanged, this);
2276        item.on('beforeshow', this.onBeforeShowItem, this);
2277    },
2278   
2279    setMainItem: function(item){
2280        item = this.getComponent(item);
2281        if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {
2282            return;
2283        }
2284       
2285        this.mainItem = item;
2286    },
2287   
2288    getMainItem: function(){
2289        return this.mainItem || null;
2290    },
2291   
2292    // private
2293    onBeforeShowItem: function(item){
2294        if (item != this.activeTab) {
2295            this.setActiveTab(item);
2296            return false;
2297        }
2298    },
2299   
2300    // private
2301    onAdd: function(gt, item, index){
2302        if (this.rendered) {
2303            this.initTab.call(this, item, index);
2304        }
2305    },
2306   
2307    // private
2308    onRemove: function(tp, item){
2309        Ext.destroy(Ext.get(this.getTabEl(item)));
2310        this.stack.remove(item);
2311        item.un('disable', this.onItemDisabled, this);
2312        item.un('enable', this.onItemEnabled, this);
2313        item.un('titlechange', this.onItemTitleChanged, this);
2314        item.un('iconchange', this.onItemIconChanged, this);
2315        item.un('beforeshow', this.onBeforeShowItem, this);
2316        if (item == this.activeTab) {
2317            var next = this.stack.next();
2318            if (next) {
2319                this.setActiveTab(next);
2320            }
2321            else if (this.items.getCount() > 0) {
2322                this.setActiveTab(0);
2323            }
2324            else {
2325                this.activeTab = null;
2326            }
2327        }
2328    },
2329   
2330    // private
2331    onBeforeAdd: function(item){
2332        var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);
2333        if (existing) {
2334            this.setActiveTab(item);
2335            return false;
2336        }
2337        Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);
2338        var es = item.elements;
2339        item.elements = es ? es.replace(',header', '') : es;
2340        item.border = (item.border === true);
2341    },
2342   
2343    // private
2344    onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,
2345    onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,
2346   
2347    // private
2348    onItemTitleChanged: function(item){
2349        var el = this.getTabEl(item);
2350        if (el) {
2351            Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;
2352        }
2353    },
2354   
2355    //private
2356    onItemIconChanged: function(item, iconCls, oldCls){
2357        var el = this.getTabEl(item);
2358        if (el) {
2359            Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);
2360        }
2361    },
2362   
2363    beforeDestroy: function(){
2364        Ext.TabPanel.prototype.beforeDestroy.call(this);
2365        this.tooltip.destroy();
2366    }
2367});
2368
2369Ext.reg('grouptab', Ext.ux.GroupTab);
2370Ext.ns('Ext.ux');
2371
2372Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {
2373    tabPosition: 'left',
2374   
2375    alternateColor: false,
2376   
2377    alternateCls: 'x-grouptabs-panel-alt',
2378   
2379    defaultType: 'grouptab',
2380   
2381    deferredRender: false,
2382   
2383    activeGroup : null,
2384   
2385    initComponent: function(){
2386        Ext.ux.GroupTabPanel.superclass.initComponent.call(this);
2387       
2388        this.addEvents(
2389            'beforegroupchange',
2390            'groupchange'
2391        );
2392        this.elements = 'body,header';
2393        this.stripTarget = 'header';
2394       
2395        this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';
2396       
2397        this.addClass('x-grouptabs-panel');
2398       
2399        if (this.tabStyle && this.tabStyle != '') {
2400            this.addClass('x-grouptabs-panel-' + this.tabStyle);
2401        }
2402       
2403        if (this.alternateColor) {
2404            this.addClass(this.alternateCls);
2405        }
2406       
2407        this.on('beforeadd', function(gtp, item, index){
2408            this.initGroup(item, index);
2409        });                 
2410    },
2411   
2412    initEvents : function() {
2413        this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);
2414    },
2415       
2416    onRender: function(ct, position){
2417        Ext.TabPanel.superclass.onRender.call(this, ct, position);
2418
2419        if(this.plain){
2420            var pos = this.tabPosition == 'top' ? 'header' : 'footer';
2421            this[pos].addClass('x-tab-panel-'+pos+'-plain');
2422        }
2423
2424        var st = this[this.stripTarget];
2425
2426        this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{
2427            tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});
2428
2429        var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);
2430        this.strip = new Ext.Element(this.stripWrap.dom.firstChild);
2431
2432                this.header.addClass('x-grouptabs-panel-header');
2433                this.bwrap.addClass('x-grouptabs-bwrap');
2434        this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');
2435
2436        if (!this.itemTpl) {
2437            var tt = new Ext.Template(
2438                '<li class="{cls}" id="{id}">', 
2439                '<a class="x-grouptabs-expand" onclick="return false;"></a>', 
2440                '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',
2441                '<span>{text}</span></a>', 
2442                '</li>'
2443            );
2444            tt.disableFormats = true;
2445            tt.compile();
2446            Ext.ux.GroupTabPanel.prototype.itemTpl = tt;
2447        }
2448
2449        this.items.each(this.initGroup, this);
2450    },
2451   
2452    afterRender: function(){
2453        Ext.ux.GroupTabPanel.superclass.afterRender.call(this);
2454       
2455        this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({
2456            cls: 'x-tab-joint'
2457        });
2458       
2459        this.addClass('x-tab-panel-' + this.tabPosition);
2460        this.header.setWidth(this.tabWidth);
2461       
2462        if (this.activeGroup !== undefined) {
2463            var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);
2464            delete this.activeGroup;
2465            this.setActiveGroup(group);
2466            group.setActiveTab(group.getMainItem());
2467        }
2468    },
2469
2470    getGroupEl : Ext.TabPanel.prototype.getTabEl,
2471       
2472    // private
2473    findTargets: function(e){
2474        var item = null;
2475        var itemEl = e.getTarget('li', this.strip);
2476        if (itemEl) {
2477            item = this.findById(itemEl.id.split(this.idDelimiter)[1]);
2478            if (item.disabled) {
2479                return {
2480                    expand: null,
2481                    item: null,
2482                    el: null
2483                };
2484            }
2485        }
2486        return {
2487            expand: e.getTarget('.x-grouptabs-expand', this.strip),
2488            isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),
2489            item: item,
2490            el: itemEl
2491        };
2492    },
2493   
2494    // private
2495    onStripMouseDown: function(e){
2496        if (e.button != 0) {
2497            return;
2498        }
2499        e.preventDefault();
2500        var t = this.findTargets(e);
2501        if (t.expand) {
2502            this.toggleGroup(t.el);
2503        }
2504        else if (t.item) {
2505            if(t.isGroup) {
2506                t.item.setActiveTab(t.item.getMainItem());
2507            }
2508            else {
2509                t.item.ownerCt.setActiveTab(t.item);
2510            }
2511        }
2512    },
2513   
2514    expandGroup: function(groupEl){
2515        if(groupEl.isXType) {
2516            groupEl = this.getGroupEl(groupEl);
2517        }
2518        Ext.fly(groupEl).addClass('x-grouptabs-expanded');
2519    },
2520   
2521    toggleGroup: function(groupEl){
2522        if(groupEl.isXType) {
2523            groupEl = this.getGroupEl(groupEl);
2524        }       
2525        Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');
2526                this.syncTabJoint();
2527    },   
2528   
2529    syncTabJoint: function(groupEl){
2530        if (!this.tabJoint) {
2531            return;
2532        }
2533       
2534        groupEl = groupEl || this.getGroupEl(this.activeGroup);
2535        if(groupEl) {
2536            this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); 
2537                       
2538            var y = Ext.isGecko2 ? 0 : 1;
2539            if (this.tabPosition == 'left'){
2540                this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);
2541            }
2542            else {
2543                this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);
2544            }           
2545        }
2546        else {
2547            this.tabJoint.hide();
2548        }
2549    },
2550   
2551    getActiveTab : function() {
2552        if(!this.activeGroup) return null;
2553        return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null; 
2554    },
2555   
2556    onResize: function(){
2557        Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);
2558        this.syncTabJoint();
2559    },
2560   
2561    createCorner: function(el, pos){
2562        return Ext.fly(el).createChild({
2563            cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos
2564        });
2565    },
2566   
2567    initGroup: function(group, index){
2568        var before = this.strip.dom.childNodes[index];       
2569        var p = this.getTemplateArgs(group);
2570        if (index === 0) {
2571            p.cls += ' x-tab-first';
2572        }
2573        p.cls += ' x-grouptabs-main';
2574        p.text = group.getMainItem().title;
2575       
2576        var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);
2577       
2578        var tl = this.createCorner(el, 'top-' + this.tabPosition);
2579        var bl = this.createCorner(el, 'bottom-' + this.tabPosition);
2580
2581        if (group.expanded) {
2582            this.expandGroup(el);
2583        }
2584
2585        if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){
2586            bl.setLeft('-10px');
2587            bl.setBottom('-5px');
2588            tl.setLeft('-10px');
2589            tl.setTop('-5px');
2590        }
2591
2592        this.mon(group, 'changemainitem', this.onGroupChangeMainItem, this);
2593        this.mon(group, 'beforetabchange', this.onGroupBeforeTabChange, this);
2594    },
2595   
2596    setActiveGroup : function(group) {
2597        group = this.getComponent(group);
2598        if(!group || this.fireEvent('beforegroupchange', this, group, this.activeGroup) === false){
2599            return;
2600        }
2601        if(!this.rendered){
2602            this.activeGroup = group;
2603            return;
2604        }
2605        if(this.activeGroup != group){
2606            if(this.activeGroup){
2607                var oldEl = this.getGroupEl(this.activeGroup);
2608                if(oldEl){
2609                    Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');
2610                }
2611                this.activeGroup.fireEvent('deactivate', this.activeTab);
2612            }
2613
2614            var groupEl = this.getGroupEl(group);
2615            Ext.fly(groupEl).addClass('x-grouptabs-strip-active');
2616                       
2617            this.activeGroup = group;
2618            this.stack.add(group);
2619
2620            this.layout.setActiveItem(group);
2621            this.syncTabJoint(groupEl);
2622
2623            group.fireEvent('activate', group);
2624            this.fireEvent('groupchange', this, group);
2625        }       
2626    },
2627   
2628    onGroupBeforeTabChange: function(group, newTab, oldTab){
2629        if(group !== this.activeGroup || newTab !== oldTab) {
2630            this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');
2631        } 
2632       
2633        this.expandGroup(this.getGroupEl(group));
2634        this.setActiveGroup(group);
2635    },
2636   
2637    getFrameHeight: function(){
2638        var h = this.el.getFrameWidth('tb');
2639        h += (this.tbar ? this.tbar.getHeight() : 0) +
2640        (this.bbar ? this.bbar.getHeight() : 0);
2641       
2642        return h;
2643    },
2644   
2645    adjustBodyWidth: function(w){
2646        return w - this.tabWidth;
2647    }
2648});
2649
2650Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/*
2651 * Note that this control will most likely remain as an example, and not as a core Ext form
2652 * control.  However, the API will be changing in a future release and so should not yet be
2653 * treated as a final, stable API at this time.
2654 */
2655
2656/**
2657 * @class Ext.ux.form.ItemSelector
2658 * @extends Ext.form.Field
2659 * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
2660 *
2661 *  @history
2662 *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
2663 *
2664 * @constructor
2665 * Create a new ItemSelector
2666 * @param {Object} config Configuration options
2667 * @xtype itemselector
2668 */
2669Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {
2670    hideNavIcons:false,
2671    imagePath:"",
2672    iconUp:"up2.gif",
2673    iconDown:"down2.gif",
2674    iconLeft:"left2.gif",
2675    iconRight:"right2.gif",
2676    iconTop:"top2.gif",
2677    iconBottom:"bottom2.gif",
2678    drawUpIcon:true,
2679    drawDownIcon:true,
2680    drawLeftIcon:true,
2681    drawRightIcon:true,
2682    drawTopIcon:true,
2683    drawBotIcon:true,
2684    delimiter:',',
2685    bodyStyle:null,
2686    border:false,
2687    defaultAutoCreate:{tag: "div"},
2688    /**
2689     * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)
2690     */
2691    multiselects:null,
2692
2693    initComponent: function(){
2694        Ext.ux.form.ItemSelector.superclass.initComponent.call(this);
2695        this.addEvents({
2696            'rowdblclick' : true,
2697            'change' : true
2698        });
2699    },
2700
2701    onRender: function(ct, position){
2702        Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);
2703
2704        // Internal default configuration for both multiselects
2705        var msConfig = [{
2706            legend: 'Available',
2707            draggable: true,
2708            droppable: true,
2709            width: 100,
2710            height: 100
2711        },{
2712            legend: 'Selected',
2713            droppable: true,
2714            draggable: true,
2715            width: 100,
2716            height: 100
2717        }];
2718
2719        this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));
2720        this.fromMultiselect.on('dblclick', this.onRowDblClick, this);
2721
2722        this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));
2723        this.toMultiselect.on('dblclick', this.onRowDblClick, this);
2724
2725        var p = new Ext.Panel({
2726            bodyStyle:this.bodyStyle,
2727            border:this.border,
2728            layout:"table",
2729            layoutConfig:{columns:3}
2730        });
2731
2732        p.add(this.fromMultiselect);
2733        var icons = new Ext.Panel({header:false});
2734        p.add(icons);
2735        p.add(this.toMultiselect);
2736        p.render(this.el);
2737        icons.el.down('.'+icons.bwrapCls).remove();
2738
2739        // ICON HELL!!!
2740        if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")
2741            this.imagePath+="/";
2742        this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');
2743        this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');
2744        this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');
2745        this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');
2746        this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');
2747        this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');
2748        var el=icons.getEl();
2749        this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});
2750        el.createChild({tag: 'br'});
2751        this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});
2752        el.createChild({tag: 'br'});
2753        this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});
2754        el.createChild({tag: 'br'});
2755        this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});
2756        el.createChild({tag: 'br'});
2757        this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});
2758        el.createChild({tag: 'br'});
2759        this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});
2760        this.toTopIcon.on('click', this.toTop, this);
2761        this.upIcon.on('click', this.up, this);
2762        this.downIcon.on('click', this.down, this);
2763        this.toBottomIcon.on('click', this.toBottom, this);
2764        this.addIcon.on('click', this.fromTo, this);
2765        this.removeIcon.on('click', this.toFrom, this);
2766        if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }
2767        if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }
2768        if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }
2769        if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }
2770        if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }
2771        if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }
2772
2773        var tb = p.body.first();
2774        this.el.setWidth(p.body.first().getWidth());
2775        p.body.removeClass();
2776
2777        this.hiddenName = this.name;
2778        var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};
2779        this.hiddenField = this.el.createChild(hiddenTag);
2780    },
2781   
2782    doLayout: function(){
2783        if(this.rendered){
2784            this.fromMultiselect.fs.doLayout();
2785            this.toMultiselect.fs.doLayout();
2786        }
2787    },
2788
2789    afterRender: function(){
2790        Ext.ux.form.ItemSelector.superclass.afterRender.call(this);
2791
2792        this.toStore = this.toMultiselect.store;
2793        this.toStore.on('add', this.valueChanged, this);
2794        this.toStore.on('remove', this.valueChanged, this);
2795        this.toStore.on('load', this.valueChanged, this);
2796        this.valueChanged(this.toStore);
2797    },
2798
2799    toTop : function() {
2800        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
2801        var records = [];
2802        if (selectionsArray.length > 0) {
2803            selectionsArray.sort();
2804            for (var i=0; i<selectionsArray.length; i++) {
2805                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
2806                records.push(record);
2807            }
2808            selectionsArray = [];
2809            for (var i=records.length-1; i>-1; i--) {
2810                record = records[i];
2811                this.toMultiselect.view.store.remove(record);
2812                this.toMultiselect.view.store.insert(0, record);
2813                selectionsArray.push(((records.length - 1) - i));
2814            }
2815        }
2816        this.toMultiselect.view.refresh();
2817        this.toMultiselect.view.select(selectionsArray);
2818    },
2819
2820    toBottom : function() {
2821        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
2822        var records = [];
2823        if (selectionsArray.length > 0) {
2824            selectionsArray.sort();
2825            for (var i=0; i<selectionsArray.length; i++) {
2826                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
2827                records.push(record);
2828            }
2829            selectionsArray = [];
2830            for (var i=0; i<records.length; i++) {
2831                record = records[i];
2832                this.toMultiselect.view.store.remove(record);
2833                this.toMultiselect.view.store.add(record);
2834                selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));
2835            }
2836        }
2837        this.toMultiselect.view.refresh();
2838        this.toMultiselect.view.select(selectionsArray);
2839    },
2840
2841    up : function() {
2842        var record = null;
2843        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
2844        selectionsArray.sort();
2845        var newSelectionsArray = [];
2846        if (selectionsArray.length > 0) {
2847            for (var i=0; i<selectionsArray.length; i++) {
2848                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
2849                if ((selectionsArray[i] - 1) >= 0) {
2850                    this.toMultiselect.view.store.remove(record);
2851                    this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);
2852                    newSelectionsArray.push(selectionsArray[i] - 1);
2853                }
2854            }
2855            this.toMultiselect.view.refresh();
2856            this.toMultiselect.view.select(newSelectionsArray);
2857        }
2858    },
2859
2860    down : function() {
2861        var record = null;
2862        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
2863        selectionsArray.sort();
2864        selectionsArray.reverse();
2865        var newSelectionsArray = [];
2866        if (selectionsArray.length > 0) {
2867            for (var i=0; i<selectionsArray.length; i++) {
2868                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
2869                if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {
2870                    this.toMultiselect.view.store.remove(record);
2871                    this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);
2872                    newSelectionsArray.push(selectionsArray[i] + 1);
2873                }
2874            }
2875            this.toMultiselect.view.refresh();
2876            this.toMultiselect.view.select(newSelectionsArray);
2877        }
2878    },
2879
2880    fromTo : function() {
2881        var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();
2882        var records = [];
2883        if (selectionsArray.length > 0) {
2884            for (var i=0; i<selectionsArray.length; i++) {
2885                record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);
2886                records.push(record);
2887            }
2888            if(!this.allowDup)selectionsArray = [];
2889            for (var i=0; i<records.length; i++) {
2890                record = records[i];
2891                if(this.allowDup){
2892                    var x=new Ext.data.Record();
2893                    record.id=x.id;
2894                    delete x;
2895                    this.toMultiselect.view.store.add(record);
2896                }else{
2897                    this.fromMultiselect.view.store.remove(record);
2898                    this.toMultiselect.view.store.add(record);
2899                    selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));
2900                }
2901            }
2902        }
2903        this.toMultiselect.view.refresh();
2904        this.fromMultiselect.view.refresh();
2905        var si = this.toMultiselect.store.sortInfo;
2906        if(si){
2907            this.toMultiselect.store.sort(si.field, si.direction);
2908        }
2909        this.toMultiselect.view.select(selectionsArray);
2910    },
2911
2912    toFrom : function() {
2913        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
2914        var records = [];
2915        if (selectionsArray.length > 0) {
2916            for (var i=0; i<selectionsArray.length; i++) {
2917                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
2918                records.push(record);
2919            }
2920            selectionsArray = [];
2921            for (var i=0; i<records.length; i++) {
2922                record = records[i];
2923                this.toMultiselect.view.store.remove(record);
2924                if(!this.allowDup){
2925                    this.fromMultiselect.view.store.add(record);
2926                    selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));
2927                }
2928            }
2929        }
2930        this.fromMultiselect.view.refresh();
2931        this.toMultiselect.view.refresh();
2932        var si = this.fromMultiselect.store.sortInfo;
2933        if (si){
2934            this.fromMultiselect.store.sort(si.field, si.direction);
2935        }
2936        this.fromMultiselect.view.select(selectionsArray);
2937    },
2938
2939    valueChanged: function(store) {
2940        var record = null;
2941        var values = [];
2942        for (var i=0; i<store.getCount(); i++) {
2943            record = store.getAt(i);
2944            values.push(record.get(this.toMultiselect.valueField));
2945        }
2946        this.hiddenField.dom.value = values.join(this.delimiter);
2947        this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
2948    },
2949
2950    getValue : function() {
2951        return this.hiddenField.dom.value;
2952    },
2953
2954    onRowDblClick : function(vw, index, node, e) {
2955        if (vw == this.toMultiselect.view){
2956            this.toFrom();
2957        } else if (vw == this.fromMultiselect.view) {
2958            this.fromTo();
2959        }
2960        return this.fireEvent('rowdblclick', vw, index, node, e);
2961    },
2962
2963    reset: function(){
2964        range = this.toMultiselect.store.getRange();
2965        this.toMultiselect.store.removeAll();
2966        this.fromMultiselect.store.add(range);
2967        var si = this.fromMultiselect.store.sortInfo;
2968        if (si){
2969            this.fromMultiselect.store.sort(si.field, si.direction);
2970        }
2971        this.valueChanged(this.toMultiselect.store);
2972    }
2973});
2974
2975Ext.reg('itemselector', Ext.ux.form.ItemSelector);
2976
2977//backwards compat
2978Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;
2979Ext.ns('Ext.ux.form');
2980
2981/**
2982 * @class Ext.ux.form.MultiSelect
2983 * @extends Ext.form.Field
2984 * A control that allows selection and form submission of multiple list items.
2985 *
2986 *  @history
2987 *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
2988 *    2008-06-19 bpm Docs and demo code clean up
2989 *
2990 * @constructor
2991 * Create a new MultiSelect
2992 * @param {Object} config Configuration options
2993 * @xtype multiselect
2994 */
2995Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {
2996    /**
2997     * @cfg {String} legend Wraps the object with a fieldset and specified legend.
2998     */
2999    /**
3000     * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.
3001     */
3002    /**
3003     * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).
3004     */
3005    /**
3006     * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).
3007     */
3008    /**
3009     * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).
3010     */
3011    ddReorder:false,
3012    /**
3013     * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a
3014     * toolbar config, or an array of buttons/button configs to be added to the toolbar.
3015     */
3016    /**
3017     * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled
3018     * (use for lists which are sorted, defaults to false).
3019     */
3020    appendOnly:false,
3021    /**
3022     * @cfg {Number} width Width in pixels of the control (defaults to 100).
3023     */
3024    width:100,
3025    /**
3026     * @cfg {Number} height Height in pixels of the control (defaults to 100).
3027     */
3028    height:100,
3029    /**
3030     * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).
3031     */
3032    displayField:0,
3033    /**
3034     * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).
3035     */
3036    valueField:1,
3037    /**
3038     * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no
3039     * selection (defaults to true).
3040     */
3041    allowBlank:true,
3042    /**
3043     * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).
3044     */
3045    minSelections:0,
3046    /**
3047     * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).
3048     */
3049    maxSelections:Number.MAX_VALUE,
3050    /**
3051     * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as
3052     * {@link Ext.form.TextField#blankText}.
3053     */
3054    blankText:Ext.form.TextField.prototype.blankText,
3055    /**
3056     * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}
3057     * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.
3058     */
3059    minSelectionsText:'Minimum {0} item(s) required',
3060    /**
3061     * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}
3062     * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.
3063     */
3064    maxSelectionsText:'Maximum {0} item(s) allowed',
3065    /**
3066     * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values
3067     * (defaults to ',').
3068     */
3069    delimiter:',',
3070    /**
3071     * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).
3072     * Acceptable values for this property are:
3073     * <div class="mdetail-params"><ul>
3074     * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
3075     * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
3076     * <div class="mdetail-params"><ul>
3077     * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
3078     * A 1-dimensional array will automatically be expanded (each array item will be the combo
3079     * {@link #valueField value} and {@link #displayField text})</div></li>
3080     * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
3081     * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
3082     * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
3083     * </div></li></ul></div></li></ul></div>
3084     */
3085
3086    // private
3087    defaultAutoCreate : {tag: "div"},
3088
3089    // private
3090    initComponent: function(){
3091        Ext.ux.form.MultiSelect.superclass.initComponent.call(this);
3092
3093        if(Ext.isArray(this.store)){
3094            if (Ext.isArray(this.store[0])){
3095                this.store = new Ext.data.ArrayStore({
3096                    fields: ['value','text'],
3097                    data: this.store
3098                });
3099                this.valueField = 'value';
3100            }else{
3101                this.store = new Ext.data.ArrayStore({
3102                    fields: ['text'],
3103                    data: this.store,
3104                    expandData: true
3105                });
3106                this.valueField = 'text';
3107            }
3108            this.displayField = 'text';
3109        } else {
3110            this.store = Ext.StoreMgr.lookup(this.store);
3111        }
3112
3113        this.addEvents({
3114            'dblclick' : true,
3115            'click' : true,
3116            'change' : true,
3117            'drop' : true
3118        });
3119    },
3120
3121    // private
3122    onRender: function(ct, position){
3123        Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);
3124
3125        var fs = this.fs = new Ext.form.FieldSet({
3126            renderTo: this.el,
3127            title: this.legend,
3128            height: this.height,
3129            width: this.width,
3130            style: "padding:0;",
3131            tbar: this.tbar,
3132            bodyStyle: 'overflow: auto;'
3133        });
3134
3135        this.view = new Ext.ListView({
3136            multiSelect: true,
3137            store: this.store,
3138            columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],
3139            hideHeaders: true
3140        });
3141
3142        fs.add(this.view);
3143
3144        this.view.on('click', this.onViewClick, this);
3145        this.view.on('beforeclick', this.onViewBeforeClick, this);
3146        this.view.on('dblclick', this.onViewDblClick, this);
3147
3148        this.hiddenName = this.name || Ext.id();
3149        var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };
3150        this.hiddenField = this.el.createChild(hiddenTag);
3151        this.hiddenField.dom.disabled = this.hiddenName != this.name;
3152        fs.doLayout();
3153    },
3154
3155    // private
3156    afterRender: function(){
3157        Ext.ux.form.MultiSelect.superclass.afterRender.call(this);
3158
3159        if (this.ddReorder && !this.dragGroup && !this.dropGroup){
3160            this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();
3161        }
3162
3163        if (this.draggable || this.dragGroup){
3164            this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {
3165                ddGroup: this.dragGroup
3166            });
3167        }
3168        if (this.droppable || this.dropGroup){
3169            this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {
3170                ddGroup: this.dropGroup
3171            });
3172        }
3173    },
3174
3175    // private
3176    onViewClick: function(vw, index, node, e) {
3177        this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
3178        this.hiddenField.dom.value = this.getValue();
3179        this.fireEvent('click', this, e);
3180        this.validate();
3181    },
3182
3183    // private
3184    onViewBeforeClick: function(vw, index, node, e) {
3185        if (this.disabled) {return false;}
3186    },
3187
3188    // private
3189    onViewDblClick : function(vw, index, node, e) {
3190        return this.fireEvent('dblclick', vw, index, node, e);
3191    },
3192
3193    /**
3194     * Returns an array of data values for the selected items in the list. The values will be separated
3195     * by {@link #delimiter}.
3196     * @return {Array} value An array of string data values
3197     */
3198    getValue: function(valueField){
3199        var returnArray = [];
3200        var selectionsArray = this.view.getSelectedIndexes();
3201        if (selectionsArray.length == 0) {return '';}
3202        for (var i=0; i<selectionsArray.length; i++) {
3203            returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));
3204        }
3205        return returnArray.join(this.delimiter);
3206    },
3207
3208    /**
3209     * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.
3210     * @param {String/Array} values The values to set
3211     */
3212    setValue: function(values) {
3213        var index;
3214        var selections = [];
3215        this.view.clearSelections();
3216        this.hiddenField.dom.value = '';
3217
3218        if (!values || (values == '')) { return; }
3219
3220        if (!Ext.isArray(values)) { values = values.split(this.delimiter); }
3221        for (var i=0; i<values.length; i++) {
3222            index = this.view.store.indexOf(this.view.store.query(this.valueField,
3223                new RegExp('^' + values[i] + '$', "i")).itemAt(0));
3224            selections.push(index);
3225        }
3226        this.view.select(selections);
3227        this.hiddenField.dom.value = this.getValue();
3228        this.validate();
3229    },
3230
3231    // inherit docs
3232    reset : function() {
3233        this.setValue('');
3234    },
3235
3236    // inherit docs
3237    getRawValue: function(valueField) {
3238        var tmp = this.getValue(valueField);
3239        if (tmp.length) {
3240            tmp = tmp.split(this.delimiter);
3241        }
3242        else {
3243            tmp = [];
3244        }
3245        return tmp;
3246    },
3247
3248    // inherit docs
3249    setRawValue: function(values){
3250        setValue(values);
3251    },
3252
3253    // inherit docs
3254    validateValue : function(value){
3255        if (value.length < 1) { // if it has no value
3256             if (this.allowBlank) {
3257                 this.clearInvalid();
3258                 return true;
3259             } else {
3260                 this.markInvalid(this.blankText);
3261                 return false;
3262             }
3263        }
3264        if (value.length < this.minSelections) {
3265            this.markInvalid(String.format(this.minSelectionsText, this.minSelections));
3266            return false;
3267        }
3268        if (value.length > this.maxSelections) {
3269            this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));
3270            return false;
3271        }
3272        return true;
3273    },
3274
3275    // inherit docs
3276    disable: function(){
3277        this.disabled = true;
3278        this.hiddenField.dom.disabled = true;
3279        this.fs.disable();
3280    },
3281
3282    // inherit docs
3283    enable: function(){
3284        this.disabled = false;
3285        this.hiddenField.dom.disabled = false;
3286        this.fs.enable();
3287    },
3288
3289    // inherit docs
3290    destroy: function(){
3291        Ext.destroy(this.fs, this.dragZone, this.dropZone);
3292        Ext.ux.form.MultiSelect.superclass.destroy.call(this);
3293    }
3294});
3295
3296
3297Ext.reg('multiselect', Ext.ux.form.MultiSelect);
3298
3299//backwards compat
3300Ext.ux.Multiselect = Ext.ux.form.MultiSelect;
3301
3302
3303Ext.ux.form.MultiSelect.DragZone = function(ms, config){
3304    this.ms = ms;
3305    this.view = ms.view;
3306    var ddGroup = config.ddGroup || 'MultiselectDD';
3307    var dd;
3308    if (Ext.isArray(ddGroup)){
3309        dd = ddGroup.shift();
3310    } else {
3311        dd = ddGroup;
3312        ddGroup = null;
3313    }
3314    Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });
3315    this.setDraggable(ddGroup);
3316};
3317
3318Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {
3319    onInitDrag : function(x, y){
3320        var el = Ext.get(this.dragData.ddel.cloneNode(true));
3321        this.proxy.update(el.dom);
3322        el.setWidth(el.child('em').getWidth());
3323        this.onStartDrag(x, y);
3324        return true;
3325    },
3326   
3327    // private
3328    collectSelection: function(data) {
3329        data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();
3330        var i = 0;
3331        this.view.store.each(function(rec){
3332            if (this.view.isSelected(i)) {
3333                var n = this.view.getNode(i);
3334                var dragNode = n.cloneNode(true);
3335                dragNode.id = Ext.id();
3336                data.ddel.appendChild(dragNode);
3337                data.records.push(this.view.store.getAt(i));
3338                data.viewNodes.push(n);
3339            }
3340            i++;
3341        }, this);
3342    },
3343
3344    // override
3345    onEndDrag: function(data, e) {
3346        var d = Ext.get(this.dragData.ddel);
3347        if (d && d.hasClass("multi-proxy")) {
3348            d.remove();
3349        }
3350    },
3351
3352    // override
3353    getDragData: function(e){
3354        var target = this.view.findItemFromChild(e.getTarget());
3355        if(target) {
3356            if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {
3357                this.view.select(target);
3358                this.ms.setValue(this.ms.getValue());
3359            }
3360            if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;
3361            var dragData = {
3362                sourceView: this.view,
3363                viewNodes: [],
3364                records: []
3365            };
3366            if (this.view.getSelectionCount() == 1) {
3367                var i = this.view.getSelectedIndexes()[0];
3368                var n = this.view.getNode(i);
3369                dragData.viewNodes.push(dragData.ddel = n);
3370                dragData.records.push(this.view.store.getAt(i));
3371                dragData.repairXY = Ext.fly(n).getXY();
3372            } else {
3373                dragData.ddel = document.createElement('div');
3374                dragData.ddel.className = 'multi-proxy';
3375                this.collectSelection(dragData);
3376            }
3377            return dragData;
3378        }
3379        return false;
3380    },
3381
3382    // override the default repairXY.
3383    getRepairXY : function(e){
3384        return this.dragData.repairXY;
3385    },
3386
3387    // private
3388    setDraggable: function(ddGroup){
3389        if (!ddGroup) return;
3390        if (Ext.isArray(ddGroup)) {
3391            Ext.each(ddGroup, this.setDraggable, this);
3392            return;
3393        }
3394        this.addToGroup(ddGroup);
3395    }
3396});
3397
3398Ext.ux.form.MultiSelect.DropZone = function(ms, config){
3399    this.ms = ms;
3400    this.view = ms.view;
3401    var ddGroup = config.ddGroup || 'MultiselectDD';
3402    var dd;
3403    if (Ext.isArray(ddGroup)){
3404        dd = ddGroup.shift();
3405    } else {
3406        dd = ddGroup;
3407        ddGroup = null;
3408    }
3409    Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });
3410    this.setDroppable(ddGroup);
3411};
3412
3413Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {
3414    /**
3415         * Part of the Ext.dd.DropZone interface. If no target node is found, the
3416         * whole Element becomes the target, and this causes the drop gesture to append.
3417         */
3418    getTargetFromEvent : function(e) {
3419        var target = e.getTarget();
3420        return target;
3421    },
3422
3423    // private
3424    getDropPoint : function(e, n, dd){
3425        if (n == this.ms.fs.body.dom) { return "below"; }
3426        var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;
3427        var c = t + (b - t) / 2;
3428        var y = Ext.lib.Event.getPageY(e);
3429        if(y <= c) {
3430            return "above";
3431        }else{
3432            return "below";
3433        }
3434    },
3435
3436    // private
3437    isValidDropPoint: function(pt, n, data) {
3438        if (!data.viewNodes || (data.viewNodes.length != 1)) {
3439            return true;
3440        }
3441        var d = data.viewNodes[0];
3442        if (d == n) {
3443            return false;
3444        }
3445        if ((pt == "below") && (n.nextSibling == d)) {
3446            return false;
3447        }
3448        if ((pt == "above") && (n.previousSibling == d)) {
3449            return false;
3450        }
3451        return true;
3452    },
3453
3454    // override
3455    onNodeEnter : function(n, dd, e, data){
3456        return false;
3457    },
3458
3459    // override
3460    onNodeOver : function(n, dd, e, data){
3461        var dragElClass = this.dropNotAllowed;
3462        var pt = this.getDropPoint(e, n, dd);
3463        if (this.isValidDropPoint(pt, n, data)) {
3464            if (this.ms.appendOnly) {
3465                return "x-tree-drop-ok-below";
3466            }
3467
3468            // set the insert point style on the target node
3469            if (pt) {
3470                var targetElClass;
3471                if (pt == "above"){
3472                    dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
3473                    targetElClass = "x-view-drag-insert-above";
3474                } else {
3475                    dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
3476                    targetElClass = "x-view-drag-insert-below";
3477                }
3478                if (this.lastInsertClass != targetElClass){
3479                    Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);
3480                    this.lastInsertClass = targetElClass;
3481                }
3482            }
3483        }
3484        return dragElClass;
3485    },
3486
3487    // private
3488    onNodeOut : function(n, dd, e, data){
3489        this.removeDropIndicators(n);
3490    },
3491
3492    // private
3493    onNodeDrop : function(n, dd, e, data){
3494        if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {
3495            return false;
3496        }
3497        var pt = this.getDropPoint(e, n, dd);
3498        if (n != this.ms.fs.body.dom)
3499            n = this.view.findItemFromChild(n);
3500        var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n);
3501        if (pt == "below") {
3502            insertAt++;
3503        }
3504
3505        var dir = false;
3506
3507        // Validate if dragging within the same MultiSelect
3508        if (data.sourceView == this.view) {
3509            // If the first element to be inserted below is the target node, remove it
3510            if (pt == "below") {
3511                if (data.viewNodes[0] == n) {
3512                    data.viewNodes.shift();
3513                }
3514            } else {  // If the last element to be inserted above is the target node, remove it
3515                if (data.viewNodes[data.viewNodes.length - 1] == n) {
3516                    data.viewNodes.pop();
3517                }
3518            }
3519
3520            // Nothing to drop...
3521            if (!data.viewNodes.length) {
3522                return false;
3523            }
3524
3525            // If we are moving DOWN, then because a store.remove() takes place first,
3526            // the insertAt must be decremented.
3527            if (insertAt > this.view.store.indexOf(data.records[0])) {
3528                dir = 'down';
3529                insertAt--;
3530            }
3531        }
3532
3533        for (var i = 0; i < data.records.length; i++) {
3534            var r = data.records[i];
3535            if (data.sourceView) {
3536                data.sourceView.store.remove(r);
3537            }
3538            this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);
3539            var si = this.view.store.sortInfo;
3540            if(si){
3541                this.view.store.sort(si.field, si.direction);
3542            }
3543        }
3544        return true;
3545    },
3546
3547    // private
3548    removeDropIndicators : function(n){
3549        if(n){
3550            Ext.fly(n).removeClass([
3551                "x-view-drag-insert-above",
3552                "x-view-drag-insert-left",
3553                "x-view-drag-insert-right",
3554                "x-view-drag-insert-below"]);
3555            this.lastInsertClass = "_noclass";
3556        }
3557    },
3558
3559    // private
3560    setDroppable: function(ddGroup){
3561        if (!ddGroup) return;
3562        if (Ext.isArray(ddGroup)) {
3563            Ext.each(ddGroup, this.setDroppable, this);
3564            return;
3565        }
3566        this.addToGroup(ddGroup);
3567    }
3568});
3569
3570/* Fix for Opera, which does not seem to include the map function on Array's */
3571if (!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
3588Ext.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 */
3595Ext.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.
3658Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
3659Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {
3660    minHeight: 0,
3661    maxHeight:10000000,
3662
3663    constructor: function(config){
3664        Ext.apply(this, config);
3665        this.events = {};
3666        Ext.ux.PanelResizer.superclass.constructor.call(this, config);
3667    },
3668
3669    init : function(p){
3670        this.panel = p;
3671
3672        if(this.panel.elements.indexOf('footer')==-1){
3673            p.elements += ',footer';
3674        }
3675        p.on('render', this.onRender, this);
3676    },
3677
3678    onRender : function(p){
3679        this.handle = p.footer.createChild({cls:'x-panel-resize'});
3680
3681        this.tracker = new Ext.dd.DragTracker({
3682            onStart: this.onDragStart.createDelegate(this),
3683            onDrag: this.onDrag.createDelegate(this),
3684            onEnd: this.onDragEnd.createDelegate(this),
3685            tolerance: 3,
3686            autoStart: 300
3687        });
3688        this.tracker.initEl(this.handle);
3689        p.on('beforedestroy', this.tracker.destroy, this.tracker);
3690    },
3691
3692        // private
3693    onDragStart: function(e){
3694        this.dragging = true;
3695        this.startHeight = this.panel.el.getHeight();
3696        this.fireEvent('dragstart', this, e);
3697    },
3698
3699        // private
3700    onDrag: function(e){
3701        this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));
3702        this.fireEvent('drag', this, e);
3703    },
3704
3705        // private
3706    onDragEnd: function(e){
3707        this.dragging = false;
3708        this.fireEvent('dragend', this, e);
3709    }
3710});
3711Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {
3712    layout : 'column',
3713    autoScroll : true,
3714    cls : 'x-portal',
3715    defaultType : 'portalcolumn',
3716   
3717    initComponent : function(){
3718        Ext.ux.Portal.superclass.initComponent.call(this);
3719        this.addEvents({
3720            validatedrop:true,
3721            beforedragover:true,
3722            dragover:true,
3723            beforedrop:true,
3724            drop:true
3725        });
3726    },
3727
3728    initEvents : function(){
3729        Ext.ux.Portal.superclass.initEvents.call(this);
3730        this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);
3731    },
3732   
3733    beforeDestroy : function() {
3734        if(this.dd){
3735            this.dd.unreg();
3736        }
3737        Ext.ux.Portal.superclass.beforeDestroy.call(this);
3738    }
3739});
3740
3741Ext.reg('portal', Ext.ux.Portal);
3742
3743
3744Ext.ux.Portal.DropZone = function(portal, cfg){
3745    this.portal = portal;
3746    Ext.dd.ScrollManager.register(portal.body);
3747    Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);
3748    portal.body.ddScrollConfig = this.ddScrollConfig;
3749};
3750
3751Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {
3752    ddScrollConfig : {
3753        vthresh: 50,
3754        hthresh: -1,
3755        animate: true,
3756        increment: 200
3757    },
3758
3759    createEvent : function(dd, e, data, col, c, pos){
3760        return {
3761            portal: this.portal,
3762            panel: data.panel,
3763            columnIndex: col,
3764            column: c,
3765            position: pos,
3766            data: data,
3767            source: dd,
3768            rawEvent: e,
3769            status: this.dropAllowed
3770        };
3771    },
3772
3773    notifyOver : function(dd, e, data){
3774        var xy = e.getXY(), portal = this.portal, px = dd.proxy;
3775
3776        // case column widths
3777        if(!this.grid){
3778            this.grid = this.getGrid();
3779        }
3780
3781        // handle case scroll where scrollbars appear during drag
3782        var cw = portal.body.dom.clientWidth;
3783        if(!this.lastCW){
3784            this.lastCW = cw;
3785        }else if(this.lastCW != cw){
3786            this.lastCW = cw;
3787            portal.doLayout();
3788            this.grid = this.getGrid();
3789        }
3790
3791        // determine column
3792        var col = 0, xs = this.grid.columnX, cmatch = false;
3793        for(var len = xs.length; col < len; col++){
3794            if(xy[0] < (xs[col].x + xs[col].w)){
3795                cmatch = true;
3796                break;
3797            }
3798        }
3799        // no match, fix last index
3800        if(!cmatch){
3801            col--;
3802        }
3803
3804        // find insert position
3805        var p, match = false, pos = 0,
3806            c = portal.items.itemAt(col),
3807            items = c.items.items, overSelf = false;
3808
3809        for(var len = items.length; pos < len; pos++){
3810            p = items[pos];
3811            var h = p.el.getHeight();
3812            if(h === 0){
3813                overSelf = true;
3814            }
3815            else if((p.el.getY()+(h/2)) > xy[1]){
3816                match = true;
3817                break;
3818            }
3819        }
3820
3821        pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);
3822        var overEvent = this.createEvent(dd, e, data, col, c, pos);
3823
3824        if(portal.fireEvent('validatedrop', overEvent) !== false &&
3825           portal.fireEvent('beforedragover', overEvent) !== false){
3826
3827            // make sure proxy width is fluid
3828            px.getProxy().setWidth('auto');
3829
3830            if(p){
3831                px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
3832            }else{
3833                px.moveProxy(c.el.dom, null);
3834            }
3835
3836            this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};
3837            this.scrollPos = portal.body.getScroll();
3838
3839            portal.fireEvent('dragover', overEvent);
3840
3841            return overEvent.status;
3842        }else{
3843            return overEvent.status;
3844        }
3845
3846    },
3847
3848    notifyOut : function(){
3849        delete this.grid;
3850    },
3851
3852    notifyDrop : function(dd, e, data){
3853        delete this.grid;
3854        if(!this.lastPos){
3855            return;
3856        }
3857        var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;
3858
3859        var dropEvent = this.createEvent(dd, e, data, col, c,
3860            pos !== false ? pos : c.items.getCount());
3861
3862        if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&
3863           this.portal.fireEvent('beforedrop', dropEvent) !== false){
3864
3865            dd.proxy.getProxy().remove();
3866            dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);
3867           
3868            if(pos !== false){
3869                if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){
3870                    pos++;
3871                }
3872                c.insert(pos, dd.panel);
3873            }else{
3874                c.add(dd.panel);
3875            }
3876           
3877            c.doLayout();
3878
3879            this.portal.fireEvent('drop', dropEvent);
3880
3881            // scroll position is lost on drop, fix it
3882            var st = this.scrollPos.top;
3883            if(st){
3884                var d = this.portal.body.dom;
3885                setTimeout(function(){
3886                    d.scrollTop = st;
3887                }, 10);
3888            }
3889
3890        }
3891        delete this.lastPos;
3892    },
3893
3894    // internal cache of body and column coords
3895    getGrid : function(){
3896        var box = this.portal.bwrap.getBox();
3897        box.columnX = [];
3898        this.portal.items.each(function(c){
3899             box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});
3900        });
3901        return box;
3902    },
3903
3904    // unregister the dropzone from ScrollManager
3905    unreg: function() {
3906        //Ext.dd.ScrollManager.unregister(this.portal.body);
3907        Ext.ux.Portal.DropZone.superclass.unreg.call(this);
3908    }
3909});
3910Ext.ux.PortalColumn = Ext.extend(Ext.Container, {
3911    layout : 'anchor',
3912    //autoEl : 'div',//already defined by Ext.Component
3913    defaultType : 'portlet',
3914    cls : 'x-portal-column'
3915});
3916
3917Ext.reg('portalcolumn', Ext.ux.PortalColumn);
3918Ext.ux.Portlet = Ext.extend(Ext.Panel, {
3919    anchor : '100%',
3920    frame : true,
3921    collapsible : true,
3922    draggable : true,
3923    cls : 'x-portlet'
3924});
3925
3926Ext.reg('portlet', Ext.ux.Portlet);
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*/
3938Ext.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});
4040Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
4041
4042Ext.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 */
4053Ext.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});
4516Ext.preg('roweditor', Ext.ux.grid.RowEditor);
4517
4518Ext.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
4541Ext.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});
4557Ext.ns('Ext.ux.grid');
4558
4559/**
4560 * @class Ext.ux.grid.RowExpander
4561 * @extends Ext.util.Observable
4562 * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
4563 * a second row body which expands/contracts.  The expand/contract behavior is configurable to react
4564 * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
4565 *
4566 * @ptype rowexpander
4567 */
4568Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {
4569    /**
4570     * @cfg {Boolean} expandOnEnter
4571     * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
4572     * key is pressed (defaults to <tt>true</tt>).
4573     */
4574    expandOnEnter : true,
4575    /**
4576     * @cfg {Boolean} expandOnDblClick
4577     * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
4578     * (defaults to <tt>true</tt>).
4579     */
4580    expandOnDblClick : true,
4581
4582    header : '',
4583    width : 20,
4584    sortable : false,
4585    fixed : true,
4586    menuDisabled : true,
4587    dataIndex : '',
4588    id : 'expander',
4589    lazyRender : true,
4590    enableCaching : true,
4591
4592    constructor: function(config){
4593        Ext.apply(this, config);
4594
4595        this.addEvents({
4596            /**
4597             * @event beforeexpand
4598             * Fires before the row expands. Have the listener return false to prevent the row from expanding.
4599             * @param {Object} this RowExpander object.
4600             * @param {Object} Ext.data.Record Record for the selected row.
4601             * @param {Object} body body element for the secondary row.
4602             * @param {Number} rowIndex The current row index.
4603             */
4604            beforeexpand: true,
4605            /**
4606             * @event expand
4607             * Fires after the row expands.
4608             * @param {Object} this RowExpander object.
4609             * @param {Object} Ext.data.Record Record for the selected row.
4610             * @param {Object} body body element for the secondary row.
4611             * @param {Number} rowIndex The current row index.
4612             */
4613            expand: true,
4614            /**
4615             * @event beforecollapse
4616             * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.
4617             * @param {Object} this RowExpander object.
4618             * @param {Object} Ext.data.Record Record for the selected row.
4619             * @param {Object} body body element for the secondary row.
4620             * @param {Number} rowIndex The current row index.
4621             */
4622            beforecollapse: true,
4623            /**
4624             * @event collapse
4625             * Fires after the row collapses.
4626             * @param {Object} this RowExpander object.
4627             * @param {Object} Ext.data.Record Record for the selected row.
4628             * @param {Object} body body element for the secondary row.
4629             * @param {Number} rowIndex The current row index.
4630             */
4631            collapse: true
4632        });
4633
4634        Ext.ux.grid.RowExpander.superclass.constructor.call(this);
4635
4636        if(this.tpl){
4637            if(typeof this.tpl == 'string'){
4638                this.tpl = new Ext.Template(this.tpl);
4639            }
4640            this.tpl.compile();
4641        }
4642
4643        this.state = {};
4644        this.bodyContent = {};
4645    },
4646
4647    getRowClass : function(record, rowIndex, p, ds){
4648        p.cols = p.cols-1;
4649        var content = this.bodyContent[record.id];
4650        if(!content && !this.lazyRender){
4651            content = this.getBodyContent(record, rowIndex);
4652        }
4653        if(content){
4654            p.body = content;
4655        }
4656        return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
4657    },
4658
4659    init : function(grid){
4660        this.grid = grid;
4661
4662        var view = grid.getView();
4663        view.getRowClass = this.getRowClass.createDelegate(this);
4664
4665        view.enableRowBody = true;
4666
4667
4668        grid.on('render', this.onRender, this);
4669        grid.on('destroy', this.onDestroy, this);
4670    },
4671
4672    // @private
4673    onRender: function() {
4674        var grid = this.grid;
4675        var mainBody = grid.getView().mainBody;
4676        mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});
4677        if (this.expandOnEnter) {
4678            this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {
4679                'enter' : this.onEnter,
4680                scope: this
4681            });
4682        }
4683        if (this.expandOnDblClick) {
4684            grid.on('rowdblclick', this.onRowDblClick, this);
4685        }
4686    },
4687   
4688    // @private   
4689    onDestroy: function() {
4690        this.keyNav.disable();
4691        delete this.keyNav;
4692        var mainBody = this.grid.getView().mainBody;
4693        mainBody.un('mousedown', this.onMouseDown, this);
4694    },
4695    // @private
4696    onRowDblClick: function(grid, rowIdx, e) {
4697        this.toggleRow(rowIdx);
4698    },
4699
4700    onEnter: function(e) {
4701        var g = this.grid;
4702        var sm = g.getSelectionModel();
4703        var sels = sm.getSelections();
4704        for (var i = 0, len = sels.length; i < len; i++) {
4705            var rowIdx = g.getStore().indexOf(sels[i]);
4706            this.toggleRow(rowIdx);
4707        }
4708    },
4709
4710    getBodyContent : function(record, index){
4711        if(!this.enableCaching){
4712            return this.tpl.apply(record.data);
4713        }
4714        var content = this.bodyContent[record.id];
4715        if(!content){
4716            content = this.tpl.apply(record.data);
4717            this.bodyContent[record.id] = content;
4718        }
4719        return content;
4720    },
4721
4722    onMouseDown : function(e, t){
4723        e.stopEvent();
4724        var row = e.getTarget('.x-grid3-row');
4725        this.toggleRow(row);
4726    },
4727
4728    renderer : function(v, p, record){
4729        p.cellAttr = 'rowspan="2"';
4730        return '<div class="x-grid3-row-expander">&#160;</div>';
4731    },
4732
4733    beforeExpand : function(record, body, rowIndex){
4734        if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){
4735            if(this.tpl && this.lazyRender){
4736                body.innerHTML = this.getBodyContent(record, rowIndex);
4737            }
4738            return true;
4739        }else{
4740            return false;
4741        }
4742    },
4743
4744    toggleRow : function(row){
4745        if(typeof row == 'number'){
4746            row = this.grid.view.getRow(row);
4747        }
4748        this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
4749    },
4750
4751    expandRow : function(row){
4752        if(typeof row == 'number'){
4753            row = this.grid.view.getRow(row);
4754        }
4755        var record = this.grid.store.getAt(row.rowIndex);
4756        var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
4757        if(this.beforeExpand(record, body, row.rowIndex)){
4758            this.state[record.id] = true;
4759            Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
4760            this.fireEvent('expand', this, record, body, row.rowIndex);
4761        }
4762    },
4763
4764    collapseRow : function(row){
4765        if(typeof row == 'number'){
4766            row = this.grid.view.getRow(row);
4767        }
4768        var record = this.grid.store.getAt(row.rowIndex);
4769        var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
4770        if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){
4771            this.state[record.id] = false;
4772            Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
4773            this.fireEvent('collapse', this, record, body, row.rowIndex);
4774        }
4775    }
4776});
4777
4778Ext.preg('rowexpander', Ext.ux.grid.RowExpander);
4779
4780//backwards compat
4781Ext.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:
4783Ext.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
4809var 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.
4827var 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 */
4846Ext.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
4902Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
4903Ext.ns('Ext.ux.form');
4904
4905Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {
4906    initComponent : function(){
4907        Ext.ux.form.SearchField.superclass.initComponent.call(this);
4908        this.on('specialkey', function(f, e){
4909            if(e.getKey() == e.ENTER){
4910                this.onTrigger2Click();
4911            }
4912        }, this);
4913    },
4914
4915    validationEvent:false,
4916    validateOnBlur:false,
4917    trigger1Class:'x-form-clear-trigger',
4918    trigger2Class:'x-form-search-trigger',
4919    hideTrigger1:true,
4920    width:180,
4921    hasSearch : false,
4922    paramName : 'query',
4923
4924    onTrigger1Click : function(){
4925        if(this.hasSearch){
4926            this.el.dom.value = '';
4927            var o = {start: 0};
4928            this.store.baseParams = this.store.baseParams || {};
4929            this.store.baseParams[this.paramName] = '';
4930            this.store.reload({params:o});
4931            this.triggers[0].hide();
4932            this.hasSearch = false;
4933        }
4934    },
4935
4936    onTrigger2Click : function(){
4937        var v = this.getRawValue();
4938        if(v.length < 1){
4939            this.onTrigger1Click();
4940            return;
4941        }
4942        var o = {start: 0};
4943        this.store.baseParams = this.store.baseParams || {};
4944        this.store.baseParams[this.paramName] = v;
4945        this.store.reload({params:o});
4946        this.hasSearch = true;
4947        this.triggers[0].show();
4948    }
4949});Ext.ns('Ext.ux.form');
4950
4951/**
4952 * @class Ext.ux.form.SelectBox
4953 * @extends Ext.form.ComboBox
4954 * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging
4955 * through the list, with item selection occurring when the mouse button is released.
4956 * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}
4957 * on inner elements.  Re-enabling editable after calling this will NOT work.</p>
4958 * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392
4959 * @history 2007-07-08 jvs
4960 * Slight mods for Ext 2.0
4961 * @xtype selectbox
4962 */
4963Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {
4964        constructor: function(config){
4965                this.searchResetDelay = 1000;
4966                config = config || {};
4967                config = Ext.apply(config || {}, {
4968                        editable: false,
4969                        forceSelection: true,
4970                        rowHeight: false,
4971                        lastSearchTerm: false,
4972                        triggerAction: 'all',
4973                        mode: 'local'
4974                });
4975
4976                Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);
4977
4978                this.lastSelectedIndex = this.selectedIndex || 0;
4979        },
4980
4981        initEvents : function(){
4982                Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);
4983                // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
4984                this.el.on('keydown', this.keySearch, this, true);
4985                this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);
4986        },
4987
4988        keySearch : function(e, target, options) {
4989                var raw = e.getKey();
4990                var key = String.fromCharCode(raw);
4991                var startIndex = 0;
4992
4993                if( !this.store.getCount() ) {
4994                        return;
4995                }
4996
4997                switch(raw) {
4998                        case Ext.EventObject.HOME:
4999                                e.stopEvent();
5000                                this.selectFirst();
5001                                return;
5002
5003                        case Ext.EventObject.END:
5004                                e.stopEvent();
5005                                this.selectLast();
5006                                return;
5007
5008                        case Ext.EventObject.PAGEDOWN:
5009                                this.selectNextPage();
5010                                e.stopEvent();
5011                                return;
5012
5013                        case Ext.EventObject.PAGEUP:
5014                                this.selectPrevPage();
5015                                e.stopEvent();
5016                                return;
5017                }
5018
5019                // skip special keys other than the shift key
5020                if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
5021                        return;
5022                }
5023                if( this.lastSearchTerm == key ) {
5024                        startIndex = this.lastSelectedIndex;
5025                }
5026                this.search(this.displayField, key, startIndex);
5027                this.cshTask.delay(this.searchResetDelay);
5028        },
5029
5030        onRender : function(ct, position) {
5031                this.store.on('load', this.calcRowsPerPage, this);
5032                Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);
5033                if( this.mode == 'local' ) {
5034                        this.calcRowsPerPage();
5035                }
5036        },
5037
5038        onSelect : function(record, index, skipCollapse){
5039                if(this.fireEvent('beforeselect', this, record, index) !== false){
5040                        this.setValue(record.data[this.valueField || this.displayField]);
5041                        if( !skipCollapse ) {
5042                                this.collapse();
5043                        }
5044                        this.lastSelectedIndex = index + 1;
5045                        this.fireEvent('select', this, record, index);
5046                }
5047        },
5048
5049        render : function(ct) {
5050                Ext.ux.form.SelectBox.superclass.render.apply(this, arguments);
5051                if( Ext.isSafari ) {
5052                        this.el.swallowEvent('mousedown', true);
5053                }
5054                this.el.unselectable();
5055                this.innerList.unselectable();
5056                this.trigger.unselectable();
5057                this.innerList.on('mouseup', function(e, target, options) {
5058                        if( target.id && target.id == this.innerList.id ) {
5059                                return;
5060                        }
5061                        this.onViewClick();
5062                }, this);
5063
5064                this.innerList.on('mouseover', function(e, target, options) {
5065                        if( target.id && target.id == this.innerList.id ) {
5066                                return;
5067                        }
5068                        this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
5069                        this.cshTask.delay(this.searchResetDelay);
5070                }, this);
5071
5072                this.trigger.un('click', this.onTriggerClick, this);
5073                this.trigger.on('mousedown', function(e, target, options) {
5074                        e.preventDefault();
5075                        this.onTriggerClick();
5076                }, this);
5077
5078                this.on('collapse', function(e, target, options) {
5079                        Ext.getDoc().un('mouseup', this.collapseIf, this);
5080                }, this, true);
5081
5082                this.on('expand', function(e, target, options) {
5083                        Ext.getDoc().on('mouseup', this.collapseIf, this);
5084                }, this, true);
5085        },
5086
5087        clearSearchHistory : function() {
5088                this.lastSelectedIndex = 0;
5089                this.lastSearchTerm = false;
5090        },
5091
5092        selectFirst : function() {
5093                this.focusAndSelect(this.store.data.first());
5094        },
5095
5096        selectLast : function() {
5097                this.focusAndSelect(this.store.data.last());
5098        },
5099
5100        selectPrevPage : function() {
5101                if( !this.rowHeight ) {
5102                        return;
5103                }
5104                var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);
5105                this.focusAndSelect(this.store.getAt(index));
5106        },
5107
5108        selectNextPage : function() {
5109                if( !this.rowHeight ) {
5110                        return;
5111                }
5112                var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);
5113                this.focusAndSelect(this.store.getAt(index));
5114        },
5115
5116        search : function(field, value, startIndex) {
5117                field = field || this.displayField;
5118                this.lastSearchTerm = value;
5119                var index = this.store.find.apply(this.store, arguments);
5120                if( index !== -1 ) {
5121                        this.focusAndSelect(index);
5122                }
5123        },
5124
5125        focusAndSelect : function(record) {
5126                var index = typeof record === 'number' ? record : this.store.indexOf(record);
5127                this.select(index, this.isExpanded());
5128                this.onSelect(this.store.getAt(record), index, this.isExpanded());
5129        },
5130
5131        calcRowsPerPage : function() {
5132                if( this.store.getCount() ) {
5133                        this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
5134                        this.rowsPerPage = this.maxHeight / this.rowHeight;
5135                } else {
5136                        this.rowHeight = false;
5137                }
5138        }
5139
5140});
5141
5142Ext.reg('selectbox', Ext.ux.form.SelectBox);
5143
5144//backwards compat
5145Ext.ux.SelectBox = Ext.ux.form.SelectBox;
5146/**
5147 * @class Ext.ux.SliderTip
5148 * @extends Ext.Tip
5149 * Simple plugin for using an Ext.Tip with a slider to show the slider value
5150 */
5151Ext.ux.SliderTip = Ext.extend(Ext.Tip, {
5152    minWidth: 10,
5153    offsets : [0, -10],
5154    init : function(slider){
5155        slider.on('dragstart', this.onSlide, this);
5156        slider.on('drag', this.onSlide, this);
5157        slider.on('dragend', this.hide, this);
5158        slider.on('destroy', this.destroy, this);
5159    },
5160
5161    onSlide : function(slider){
5162        this.show();
5163        this.body.update(this.getText(slider));
5164        this.doAutoWidth();
5165        this.el.alignTo(slider.thumb, 'b-t?', this.offsets);
5166    },
5167
5168    getText : function(slider){
5169        return String(slider.getValue());
5170    }
5171});
5172Ext.ux.SlidingPager = Ext.extend(Object, {
5173    init : function(pbar){
5174        Ext.each(pbar.items.getRange(2,6), function(c){
5175            c.hide();
5176        });
5177        var slider = new Ext.Slider({
5178            width: 114,
5179            minValue: 1,
5180            maxValue: 1,
5181            plugins: new Ext.ux.SliderTip({
5182                getText : function(s){
5183                    return String.format('Page <b>{0}</b> of <b>{1}</b>', s.value, s.maxValue);
5184                }
5185            }),
5186            listeners: {
5187                changecomplete: function(s, v){
5188                    pbar.changePage(v);
5189                }
5190            }
5191        });
5192        pbar.insert(5, slider);
5193        pbar.on({
5194            change: function(pb, data){
5195                slider.maxValue = data.pages;
5196                slider.setValue(data.activePage);
5197            },
5198            beforedestroy: function(){
5199                slider.destroy();
5200            }
5201        });
5202    }
5203});Ext.ns('Ext.ux.form');
5204
5205/**
5206 * @class Ext.ux.form.SpinnerField
5207 * @extends Ext.form.NumberField
5208 * Creates a field utilizing Ext.ux.Spinner
5209 * @xtype spinnerfield
5210 */
5211Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
5212    deferHeight: true,
5213    autoSize: Ext.emptyFn,
5214    onBlur: Ext.emptyFn,
5215    adjustSize: Ext.BoxComponent.prototype.adjustSize,
5216
5217        constructor: function(config) {
5218                var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');
5219
5220                var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);
5221
5222                var plugins = config.plugins
5223                        ? (Ext.isArray(config.plugins)
5224                                ? config.plugins.push(spl)
5225                                : [config.plugins, spl])
5226                        : spl;
5227
5228                Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));
5229        },
5230
5231    onShow: function(){
5232        if (this.wrap) {
5233            this.wrap.dom.style.display = '';
5234            this.wrap.dom.style.visibility = 'visible';
5235        }
5236    },
5237
5238    onHide: function(){
5239        this.wrap.dom.style.display = 'none';
5240    },
5241
5242    // private
5243    getResizeEl: function(){
5244        return this.wrap;
5245    },
5246
5247    // private
5248    getPositionEl: function(){
5249        return this.wrap;
5250    },
5251
5252    // private
5253    alignErrorIcon: function(){
5254        if (this.wrap) {
5255            this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
5256        }
5257    },
5258
5259    validateBlur: function(){
5260        return true;
5261    }
5262});
5263
5264Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);
5265
5266//backwards compat
5267Ext.form.SpinnerField = Ext.ux.form.SpinnerField;
5268/**
5269 * @class Ext.ux.Spinner
5270 * @extends Ext.util.Observable
5271 * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
5272 */
5273Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
5274    incrementValue: 1,
5275    alternateIncrementValue: 5,
5276    triggerClass: 'x-form-spinner-trigger',
5277    splitterClass: 'x-form-spinner-splitter',
5278    alternateKey: Ext.EventObject.shiftKey,
5279    defaultValue: 0,
5280    accelerate: false,
5281
5282    constructor: function(config){
5283        Ext.ux.Spinner.superclass.constructor.call(this, config);
5284        Ext.apply(this, config);
5285        this.mimicing = false;
5286    },
5287
5288    init: function(field){
5289        this.field = field;
5290
5291        field.afterMethod('onRender', this.doRender, this);
5292        field.afterMethod('onEnable', this.doEnable, this);
5293        field.afterMethod('onDisable', this.doDisable, this);
5294        field.afterMethod('afterRender', this.doAfterRender, this);
5295        field.afterMethod('onResize', this.doResize, this);
5296        field.afterMethod('onFocus', this.doFocus, this);
5297        field.beforeMethod('onDestroy', this.doDestroy, this);
5298    },
5299
5300    doRender: function(ct, position){
5301        var el = this.el = this.field.getEl();
5302        var f = this.field;
5303
5304        if (!f.wrap) {
5305            f.wrap = this.wrap = el.wrap({
5306                cls: "x-form-field-wrap"
5307            });
5308        }
5309        else {
5310            this.wrap = f.wrap.addClass('x-form-field-wrap');
5311        }
5312
5313        this.trigger = this.wrap.createChild({
5314            tag: "img",
5315            src: Ext.BLANK_IMAGE_URL,
5316            cls: "x-form-trigger " + this.triggerClass
5317        });
5318
5319        if (!f.width) {
5320            this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
5321        }
5322
5323        this.splitter = this.wrap.createChild({
5324            tag: 'div',
5325            cls: this.splitterClass,
5326            style: 'width:13px; height:2px;'
5327        });
5328        this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();
5329
5330        this.proxy = this.trigger.createProxy('', this.splitter, true);
5331        this.proxy.addClass("x-form-spinner-proxy");
5332        this.proxy.setStyle('left', '0px');
5333        this.proxy.setSize(14, 1);
5334        this.proxy.hide();
5335        this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {
5336            dragElId: this.proxy.id
5337        });
5338
5339        this.initTrigger();
5340        this.initSpinner();
5341    },
5342
5343    doAfterRender: function(){
5344        var y;
5345        if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
5346            this.el.position();
5347            this.el.setY(y);
5348        }
5349    },
5350
5351    doEnable: function(){
5352        if (this.wrap) {
5353            this.wrap.removeClass(this.field.disabledClass);
5354        }
5355    },
5356
5357    doDisable: function(){
5358        if (this.wrap) {
5359            this.wrap.addClass(this.field.disabledClass);
5360            this.el.removeClass(this.field.disabledClass);
5361        }
5362    },
5363
5364    doResize: function(w, h){
5365        if (typeof w == 'number') {
5366            this.el.setWidth(this.field.adjustWidth('input', w - this.trigger.getWidth()));
5367        }
5368        this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
5369    },
5370
5371    doFocus: function(){
5372        if (!this.mimicing) {
5373            this.wrap.addClass('x-trigger-wrap-focus');
5374            this.mimicing = true;
5375            Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {
5376                delay: 10
5377            });
5378            this.el.on('keydown', this.checkTab, this);
5379        }
5380    },
5381
5382    // private
5383    checkTab: function(e){
5384        if (e.getKey() == e.TAB) {
5385            this.triggerBlur();
5386        }
5387    },
5388
5389    // private
5390    mimicBlur: function(e){
5391        if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
5392            this.triggerBlur();
5393        }
5394    },
5395
5396    // private
5397    triggerBlur: function(){
5398        this.mimicing = false;
5399        Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
5400        this.el.un("keydown", this.checkTab, this);
5401        this.field.beforeBlur();
5402        this.wrap.removeClass('x-trigger-wrap-focus');
5403        this.field.onBlur.call(this.field);
5404    },
5405
5406    initTrigger: function(){
5407        this.trigger.addClassOnOver('x-form-trigger-over');
5408        this.trigger.addClassOnClick('x-form-trigger-click');
5409    },
5410
5411    initSpinner: function(){
5412        this.field.addEvents({
5413            'spin': true,
5414            'spinup': true,
5415            'spindown': true
5416        });
5417
5418        this.keyNav = new Ext.KeyNav(this.el, {
5419            "up": function(e){
5420                e.preventDefault();
5421                this.onSpinUp();
5422            },
5423
5424            "down": function(e){
5425                e.preventDefault();
5426                this.onSpinDown();
5427            },
5428
5429            "pageUp": function(e){
5430                e.preventDefault();
5431                this.onSpinUpAlternate();
5432            },
5433
5434            "pageDown": function(e){
5435                e.preventDefault();
5436                this.onSpinDownAlternate();
5437            },
5438
5439            scope: this
5440        });
5441
5442        this.repeater = new Ext.util.ClickRepeater(this.trigger, {
5443            accelerate: this.accelerate
5444        });
5445        this.field.mon(this.repeater, "click", this.onTriggerClick, this, {
5446            preventDefault: true
5447        });
5448
5449        this.field.mon(this.trigger, {
5450            mouseover: this.onMouseOver,
5451            mouseout: this.onMouseOut,
5452            mousemove: this.onMouseMove,
5453            mousedown: this.onMouseDown,
5454            mouseup: this.onMouseUp,
5455            scope: this,
5456            preventDefault: true
5457        });
5458
5459        this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);
5460
5461        this.dd.setXConstraint(0, 0, 10)
5462        this.dd.setYConstraint(1500, 1500, 10);
5463        this.dd.endDrag = this.endDrag.createDelegate(this);
5464        this.dd.startDrag = this.startDrag.createDelegate(this);
5465        this.dd.onDrag = this.onDrag.createDelegate(this);
5466    },
5467
5468    onMouseOver: function(){
5469        if (this.disabled) {
5470            return;
5471        }
5472        var middle = this.getMiddle();
5473        this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
5474        this.trigger.addClass(this.tmpHoverClass);
5475    },
5476
5477    //private
5478    onMouseOut: function(){
5479        this.trigger.removeClass(this.tmpHoverClass);
5480    },
5481
5482    //private
5483    onMouseMove: function(){
5484        if (this.disabled) {
5485            return;
5486        }
5487        var middle = this.getMiddle();
5488        if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||
5489        ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {
5490        }
5491    },
5492
5493    //private
5494    onMouseDown: function(){
5495        if (this.disabled) {
5496            return;
5497        }
5498        var middle = this.getMiddle();
5499        this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
5500        this.trigger.addClass(this.tmpClickClass);
5501    },
5502
5503    //private
5504    onMouseUp: function(){
5505        this.trigger.removeClass(this.tmpClickClass);
5506    },
5507
5508    //private
5509    onTriggerClick: function(){
5510        if (this.disabled || this.el.dom.readOnly) {
5511            return;
5512        }
5513        var middle = this.getMiddle();
5514        var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
5515        this['onSpin' + ud]();
5516    },
5517
5518    //private
5519    getMiddle: function(){
5520        var t = this.trigger.getTop();
5521        var h = this.trigger.getHeight();
5522        var middle = t + (h / 2);
5523        return middle;
5524    },
5525
5526    //private
5527    //checks if control is allowed to spin
5528    isSpinnable: function(){
5529        if (this.disabled || this.el.dom.readOnly) {
5530            Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
5531            return false;
5532        }
5533        return true;
5534    },
5535
5536    handleMouseWheel: function(e){
5537        //disable scrolling when not focused
5538        if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
5539            return;
5540        }
5541
5542        var delta = e.getWheelDelta();
5543        if (delta > 0) {
5544            this.onSpinUp();
5545            e.stopEvent();
5546        }
5547        else
5548            if (delta < 0) {
5549                this.onSpinDown();
5550                e.stopEvent();
5551            }
5552    },
5553
5554    //private
5555    startDrag: function(){
5556        this.proxy.show();
5557        this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
5558    },
5559
5560    //private
5561    endDrag: function(){
5562        this.proxy.hide();
5563    },
5564
5565    //private
5566    onDrag: function(){
5567        if (this.disabled) {
5568            return;
5569        }
5570        var y = Ext.fly(this.dd.getDragEl()).getTop();
5571        var ud = '';
5572
5573        if (this._previousY > y) {
5574            ud = 'Up';
5575        } //up
5576        if (this._previousY < y) {
5577            ud = 'Down';
5578        } //down
5579        if (ud != '') {
5580            this['onSpin' + ud]();
5581        }
5582
5583        this._previousY = y;
5584    },
5585
5586    //private
5587    onSpinUp: function(){
5588        if (this.isSpinnable() == false) {
5589            return;
5590        }
5591        if (Ext.EventObject.shiftKey == true) {
5592            this.onSpinUpAlternate();
5593            return;
5594        }
5595        else {
5596            this.spin(false, false);
5597        }
5598        this.field.fireEvent("spin", this);
5599        this.field.fireEvent("spinup", this);
5600    },
5601
5602    //private
5603    onSpinDown: function(){
5604        if (this.isSpinnable() == false) {
5605            return;
5606        }
5607        if (Ext.EventObject.shiftKey == true) {
5608            this.onSpinDownAlternate();
5609            return;
5610        }
5611        else {
5612            this.spin(true, false);
5613        }
5614        this.field.fireEvent("spin", this);
5615        this.field.fireEvent("spindown", this);
5616    },
5617
5618    //private
5619    onSpinUpAlternate: function(){
5620        if (this.isSpinnable() == false) {
5621            return;
5622        }
5623        this.spin(false, true);
5624        this.field.fireEvent("spin", this);
5625        this.field.fireEvent("spinup", this);
5626    },
5627
5628    //private
5629    onSpinDownAlternate: function(){
5630        if (this.isSpinnable() == false) {
5631            return;
5632        }
5633        this.spin(true, true);
5634        this.field.fireEvent("spin", this);
5635        this.field.fireEvent("spindown", this);
5636    },
5637
5638    spin: function(down, alternate){
5639        var v = parseFloat(this.field.getValue());
5640        var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
5641        (down == true) ? v -= incr : v += incr;
5642
5643        v = (isNaN(v)) ? this.defaultValue : v;
5644        v = this.fixBoundries(v);
5645        this.field.setRawValue(v);
5646    },
5647
5648    fixBoundries: function(value){
5649        var v = value;
5650
5651        if (this.field.minValue != undefined && v < this.field.minValue) {
5652            v = this.field.minValue;
5653        }
5654        if (this.field.maxValue != undefined && v > this.field.maxValue) {
5655            v = this.field.maxValue;
5656        }
5657
5658        return this.fixPrecision(v);
5659    },
5660
5661    // private
5662    fixPrecision: function(value){
5663        var nan = isNaN(value);
5664        if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {
5665            return nan ? '' : value;
5666        }
5667        return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));
5668    },
5669
5670    doDestroy: function(){
5671        if (this.trigger) {
5672            this.trigger.remove();
5673        }
5674        if (this.wrap) {
5675            this.wrap.remove();
5676            delete this.field.wrap;
5677        }
5678
5679        if (this.splitter) {
5680            this.splitter.remove();
5681        }
5682
5683        if (this.dd) {
5684            this.dd.unreg();
5685            this.dd = null;
5686        }
5687
5688        if (this.proxy) {
5689            this.proxy.remove();
5690        }
5691
5692        if (this.repeater) {
5693            this.repeater.purgeListeners();
5694        }
5695    }
5696});
5697
5698//backwards compat
5699Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){
5700    Ext.apply(this, config);
5701}
5702Ext.ux.Spotlight.prototype = {
5703    active : false,
5704    animate : true,
5705    duration: .25,
5706    easing:'easeNone',
5707
5708    // private
5709    animated : false,
5710
5711    createElements : function(){
5712        var bd = Ext.getBody();
5713
5714        this.right = bd.createChild({cls:'x-spotlight'});
5715        this.left = bd.createChild({cls:'x-spotlight'});
5716        this.top = bd.createChild({cls:'x-spotlight'});
5717        this.bottom = bd.createChild({cls:'x-spotlight'});
5718
5719        this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);
5720    },
5721
5722    show : function(el, callback, scope){
5723        if(this.animated){
5724            this.show.defer(50, this, [el, callback, scope]);
5725            return;
5726        }
5727        this.el = Ext.get(el);
5728        if(!this.right){
5729            this.createElements();
5730        }
5731        if(!this.active){
5732            this.all.setDisplayed('');
5733            this.applyBounds(true, false);
5734            this.active = true;
5735            Ext.EventManager.onWindowResize(this.syncSize, this);
5736            this.applyBounds(false, this.animate, false, callback, scope);
5737        }else{
5738            this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous
5739        }
5740    },
5741
5742    hide : function(callback, scope){
5743        if(this.animated){
5744            this.hide.defer(50, this, [callback, scope]);
5745            return;
5746        }
5747        Ext.EventManager.removeResizeListener(this.syncSize, this);
5748        this.applyBounds(true, this.animate, true, callback, scope);
5749    },
5750
5751    doHide : function(){
5752        this.active = false;
5753        this.all.setDisplayed(false);
5754    },
5755
5756    syncSize : function(){
5757        this.applyBounds(false, false);
5758    },
5759
5760    applyBounds : function(basePts, anim, doHide, callback, scope){
5761
5762        var rg = this.el.getRegion();
5763
5764        var dw = Ext.lib.Dom.getViewWidth(true);
5765        var dh = Ext.lib.Dom.getViewHeight(true);
5766
5767        var c = 0, cb = false;
5768        if(anim){
5769            cb = {
5770                callback: function(){
5771                    c++;
5772                    if(c == 4){
5773                        this.animated = false;
5774                        if(doHide){
5775                            this.doHide();
5776                        }
5777                        Ext.callback(callback, scope, [this]);
5778                    }
5779                },
5780                scope: this,
5781                duration: this.duration,
5782                easing: this.easing
5783            };
5784            this.animated = true;
5785        }
5786
5787        this.right.setBounds(
5788                rg.right,
5789                basePts ? dh : rg.top,
5790                dw - rg.right,
5791                basePts ? 0 : (dh - rg.top),
5792                cb);
5793
5794        this.left.setBounds(
5795                0,
5796                0,
5797                rg.left,
5798                basePts ? 0 : rg.bottom,
5799                cb);
5800
5801        this.top.setBounds(
5802                basePts ? dw : rg.left,
5803                0,
5804                basePts ? 0 : dw - rg.left,
5805                rg.top,
5806                cb);
5807
5808        this.bottom.setBounds(
5809                0,
5810                rg.bottom,
5811                basePts ? 0 : rg.right,
5812                dh - rg.bottom,
5813                cb);
5814
5815        if(!anim){
5816            if(doHide){
5817                this.doHide();
5818            }
5819            if(callback){
5820                Ext.callback(callback, scope, [this]);
5821            }
5822        }
5823    },
5824
5825    destroy : function(){
5826        this.doHide();
5827        Ext.destroy(
5828            this.right,
5829            this.left,
5830            this.top,
5831            this.bottom);
5832        delete this.el;
5833        delete this.all;
5834    }
5835};
5836
5837//backwards compat
5838Ext.Spotlight = Ext.ux.Spotlight;/**
5839 * @class Ext.ux.TabCloseMenu
5840 * @extends Object
5841 * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.
5842 *
5843 * @ptype tabclosemenu
5844 */
5845Ext.ux.TabCloseMenu = function(){
5846    var tabs, menu, ctxItem;
5847    this.init = function(tp){
5848        tabs = tp;
5849        tabs.on('contextmenu', onContextMenu);
5850    };
5851
5852    function onContextMenu(ts, item, e){
5853        if(!menu){ // create context menu on first right click
5854            menu = new Ext.menu.Menu({           
5855            items: [{
5856                id: tabs.id + '-close',
5857                text: 'Close Tab',
5858                handler : function(){
5859                    tabs.remove(ctxItem);
5860                }
5861            },{
5862                id: tabs.id + '-close-others',
5863                text: 'Close Other Tabs',
5864                handler : function(){
5865                    tabs.items.each(function(item){
5866                        if(item.closable && item != ctxItem){
5867                            tabs.remove(item);
5868                        }
5869                    });
5870                }
5871            }]});
5872        }
5873        ctxItem = item;
5874        var items = menu.items;
5875        items.get(tabs.id + '-close').setDisabled(!item.closable);
5876        var disableOthers = true;
5877        tabs.items.each(function(){
5878            if(this != item && this.closable){
5879                disableOthers = false;
5880                return false;
5881            }
5882        });
5883        items.get(tabs.id + '-close-others').setDisabled(disableOthers);
5884        e.stopEvent();
5885        menu.showAt(e.getPoint());
5886    }
5887};
5888
5889Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);
5890Ext.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 */
5905Ext.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
5967Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
5968
5969//backwards compat
5970Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
5971
5972
5973Ext.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});
6150Ext.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 */
6167Ext.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
6251Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;
Note: See TracBrowser for help on using the repository browser.