X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/examples/ux/ux-all-debug.js diff --git a/examples/ux/ux-all-debug.js b/examples/ux/ux-all-debug.js new file mode 100644 index 00000000..6ef28d4d --- /dev/null +++ b/examples/ux/ux-all-debug.js @@ -0,0 +1,6251 @@ +/*! + * Ext JS Library 3.0.0 + * Copyright(c) 2006-2009 Ext JS, LLC + * licensing@extjs.com + * http://www.extjs.com/license + */ +Ext.ns('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.BufferView + * @extends Ext.grid.GridView + * A custom GridView which renders rows on an as-needed basis. + */ +Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, { + /** + * @cfg {Number} rowHeight + * The height of a row in the grid. + */ + rowHeight: 19, + + /** + * @cfg {Number} borderHeight + * The combined height of border-top and border-bottom of a row. + */ + borderHeight: 2, + + /** + * @cfg {Boolean/Number} scrollDelay + * The number of milliseconds before rendering rows out of the visible + * viewing area. Defaults to 100. Rows will render immediately with a config + * of false. + */ + scrollDelay: 100, + + /** + * @cfg {Number} cacheSize + * The number of rows to look forward and backwards from the currently viewable + * area. The cache applies only to rows that have been rendered already. + */ + cacheSize: 20, + + /** + * @cfg {Number} cleanDelay + * The number of milliseconds to buffer cleaning of extra rows not in the + * cache. + */ + cleanDelay: 500, + + initTemplates : function(){ + Ext.ux.grid.BufferView.superclass.initTemplates.call(this); + var ts = this.templates; + // empty div to act as a place holder for a row + ts.rowHolder = new Ext.Template( + '
' + ); + ts.rowHolder.disableFormats = true; + ts.rowHolder.compile(); + + ts.rowBody = new Ext.Template( + '', + '{cells}', + (this.enableRowBody ? '' : ''), + '
{body}
' + ); + ts.rowBody.disableFormats = true; + ts.rowBody.compile(); + }, + + getStyleRowHeight : function(){ + return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight; + }, + + getCalculatedRowHeight : function(){ + return this.rowHeight + this.borderHeight; + }, + + getVisibleRowCount : function(){ + var rh = this.getCalculatedRowHeight(); + var visibleHeight = this.scroller.dom.clientHeight; + return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh); + }, + + getVisibleRows: function(){ + var count = this.getVisibleRowCount(); + var sc = this.scroller.dom.scrollTop; + var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-1); + return { + first: Math.max(start, 0), + last: Math.min(start + count + 2, this.ds.getCount()-1) + }; + }, + + doRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){ + var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1; + var rh = this.getStyleRowHeight(); + var vr = this.getVisibleRows(); + var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;'; + // buffers + var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r; + for (var j = 0, len = rs.length; j < len; j++) { + r = rs[j]; cb = []; + var rowIndex = (j+startRow); + var visible = rowIndex >= vr.first && rowIndex <= vr.last; + if (visible) { + for (var i = 0; i < colCount; i++) { + c = cs[i]; + p.id = c.id; + p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : ''); + p.attr = p.cellAttr = ""; + p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds); + p.style = c.style; + if (p.value == undefined || p.value === "") { + p.value = " "; + } + if (r.dirty && typeof r.modified[c.name] !== 'undefined') { + p.css += ' x-grid3-dirty-cell'; + } + cb[cb.length] = ct.apply(p); + } + } + var alt = []; + if(stripe && ((rowIndex+1) % 2 == 0)){ + alt[0] = "x-grid3-row-alt"; + } + if(r.dirty){ + alt[1] = " x-grid3-dirty-row"; + } + rp.cols = colCount; + if(this.getRowClass){ + alt[2] = this.getRowClass(r, rowIndex, rp, ds); + } + rp.alt = alt.join(" "); + rp.cells = cb.join(""); + buf[buf.length] = !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp)); + } + return buf.join(""); + }, + + isRowRendered: function(index){ + var row = this.getRow(index); + return row && row.childNodes.length > 0; + }, + + syncScroll: function(){ + Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments); + this.update(); + }, + + // a (optionally) buffered method to update contents of gridview + update: function(){ + if (this.scrollDelay) { + if (!this.renderTask) { + this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this); + } + this.renderTask.delay(this.scrollDelay); + }else{ + this.doUpdate(); + } + }, + + doUpdate: function(){ + if (this.getVisibleRowCount() > 0) { + var g = this.grid, cm = g.colModel, ds = g.store; + var cs = this.getColumnData(); + + var vr = this.getVisibleRows(); + for (var i = vr.first; i <= vr.last; i++) { + // if row is NOT rendered and is visible, render it + if(!this.isRowRendered(i)){ + var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true); + this.getRow(i).innerHTML = html; + } + } + this.clean(); + } + }, + + // a buffered method to clean rows + clean : function(){ + if(!this.cleanTask){ + this.cleanTask = new Ext.util.DelayedTask(this.doClean, this); + } + this.cleanTask.delay(this.cleanDelay); + }, + + doClean: function(){ + if (this.getVisibleRowCount() > 0) { + var vr = this.getVisibleRows(); + vr.first -= this.cacheSize; + vr.last += this.cacheSize; + + var i = 0, rows = this.getRows(); + // if first is less than 0, all rows have been rendered + // so lets clean the end... + if(vr.first <= 0){ + i = vr.last + 1; + } + for(var len = this.ds.getCount(); i < len; i++){ + // if current row is outside of first and last and + // has content, update the innerHTML to nothing + if ((i < vr.first || i > vr.last) && rows[i].innerHTML) { + rows[i].innerHTML = ''; + } + } + } + }, + + layout: function(){ + Ext.ux.grid.BufferView.superclass.layout.call(this); + this.update(); + } +}); +// We are adding these custom layouts to a namespace that does not +// exist by default in Ext, so we have to add the namespace first: +Ext.ns('Ext.ux.layout'); + +/** + * @class Ext.ux.layout.CenterLayout + * @extends Ext.layout.FitLayout + *

This is a very simple layout style used to center contents within a container. This layout works within + * nested containers and can also be used as expected as a Viewport layout to center the page layout.

+ *

As a subclass of FitLayout, CenterLayout expects to have a single child panel of the container that uses + * the layout. The layout does not require any config options, although the child panel contained within the + * layout must provide a fixed or percentage width. The child panel's height will fit to the container by + * default, but you can specify autoHeight:true to allow it to autosize based on its content height. + * Example usage:

+ *

+// The content panel is centered in the container
+var p = new Ext.Panel({
+    title: 'Center Layout',
+    layout: 'ux.center',
+    items: [{
+        title: 'Centered Content',
+        width: '75%',
+        html: 'Some content'
+    }]
+});
+
+// If you leave the title blank and specify no border
+// you'll create a non-visual, structural panel just
+// for centering the contents in the main container.
+var p = new Ext.Panel({
+    layout: 'ux.center',
+    border: false,
+    items: [{
+        title: 'Centered Content',
+        width: 300,
+        autoHeight: true,
+        html: 'Some content'
+    }]
+});
+
+ */ +Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, { + // private + setItemSize : function(item, size){ + this.container.addClass('ux-layout-center'); + item.addClass('ux-layout-center-item'); + if(item && size.height > 0){ + if(item.width){ + size.width = item.width; + } + item.setSize(size); + } + } +}); + +Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout; +Ext.ns('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.CheckColumn + * @extends Object + * GridPanel plugin to add a column with check boxes to a grid. + *

Example usage:

+ *

+// create the column
+var checkColumn = new Ext.grid.CheckColumn({
+   header: 'Indoor?',
+   dataIndex: 'indoor',
+   id: 'check',
+   width: 55
+});
+
+// add the column to the column model
+var cm = new Ext.grid.ColumnModel([{
+       header: 'Foo',
+       ...
+    },
+    checkColumn
+]);
+
+// create the grid
+var grid = new Ext.grid.EditorGridPanel({
+    ...
+    cm: cm,
+    plugins: [checkColumn], // include plugin
+    ...
+});
+ * 
+ * In addition to storing a Boolean value within the record data, this + * class toggles a css class between 'x-grid3-check-col' and + * 'x-grid3-check-col-on' to alter the background image used for + * a column. + */ +Ext.ux.grid.CheckColumn = function(config){ + Ext.apply(this, config); + if(!this.id){ + this.id = Ext.id(); + } + this.renderer = this.renderer.createDelegate(this); +}; + +Ext.ux.grid.CheckColumn.prototype ={ + init : function(grid){ + this.grid = grid; + this.grid.on('render', function(){ + var view = this.grid.getView(); + view.mainBody.on('mousedown', this.onMouseDown, this); + }, this); + }, + + onMouseDown : function(e, t){ + if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){ + e.stopEvent(); + var index = this.grid.getView().findRowIndex(t); + var record = this.grid.store.getAt(index); + record.set(this.dataIndex, !record.data[this.dataIndex]); + } + }, + + renderer : function(v, p, record){ + p.css += ' x-grid3-check-col-td'; + return '
 
'; + } +}; + +// register ptype +Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn); + +// backwards compat +Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.tree'); + +/** + * @class Ext.ux.tree.ColumnTree + * @extends Ext.tree.TreePanel + * + * @xtype columntree + */ +Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, { + lines : false, + borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell + cls : 'x-column-tree', + + onRender : function(){ + Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments); + this.headers = this.header.createChild({cls:'x-tree-headers'}); + + var cols = this.columns, c; + var totalWidth = 0; + var scrollOffset = 19; // similar to Ext.grid.GridView default + + for(var i = 0, len = cols.length; i < len; i++){ + c = cols[i]; + totalWidth += c.width; + this.headers.createChild({ + cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''), + cn: { + cls:'x-tree-hd-text', + html: c.header + }, + style:'width:'+(c.width-this.borderWidth)+'px;' + }); + } + this.headers.createChild({cls:'x-clear'}); + // prevent floats from wrapping when clipped + this.headers.setWidth(totalWidth+scrollOffset); + this.innerCt.setWidth(totalWidth); + } +}); + +Ext.reg('columntree', Ext.ux.tree.ColumnTree); + +//backwards compat +Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree; + + +/** + * @class Ext.ux.tree.ColumnNodeUI + * @extends Ext.tree.TreeNodeUI + */ +Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { + focus: Ext.emptyFn, // prevent odd scrolling behavior + + renderElements : function(n, a, targetNode, bulkRender){ + this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : ''; + + var t = n.getOwnerTree(); + var cols = t.columns; + var bw = t.borderWidth; + var c = cols[0]; + + var buf = [ + '
  • ', + '"]; + for(var i = 1, len = cols.length; i < len; i++){ + c = cols[i]; + + buf.push('
    ', + '
    ',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"
    ", + "
    "); + } + buf.push( + '
    ', + '', + "
  • "); + + if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){ + this.wrap = Ext.DomHelper.insertHtml("beforeBegin", + n.nextSibling.ui.getEl(), buf.join("")); + }else{ + this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join("")); + } + + this.elNode = this.wrap.childNodes[0]; + this.ctNode = this.wrap.childNodes[1]; + var cs = this.elNode.firstChild.childNodes; + this.indentNode = cs[0]; + this.ecNode = cs[1]; + this.iconNode = cs[2]; + this.anchor = cs[3]; + this.textNode = cs[3].firstChild; + } +}); + +//backwards compat +Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI; +/** + * @class Ext.DataView.LabelEditor + * @extends Ext.Editor + * + */ +Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, { + alignment: "tl-tl", + hideEl : false, + cls: "x-small-editor", + shim: false, + completeOnEnter: true, + cancelOnEsc: true, + labelSelector: 'span.x-editable', + + constructor: function(cfg, field){ + Ext.DataView.LabelEditor.superclass.constructor.call(this, + field || new Ext.form.TextField({ + allowBlank: false, + growMin:90, + growMax:240, + grow:true, + selectOnFocus:true + }), cfg + ); + }, + + init : function(view){ + this.view = view; + view.on('render', this.initEditor, this); + this.on('complete', this.onSave, this); + }, + + initEditor : function(){ + this.view.on({ + scope: this, + containerclick: this.doBlur, + click: this.doBlur + }); + this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector}); + }, + + doBlur: function(){ + if(this.editing){ + this.field.blur(); + } + }, + + onMouseDown : function(e, target){ + if(!e.ctrlKey && !e.shiftKey){ + var item = this.view.findItemFromChild(target); + e.stopEvent(); + var record = this.view.store.getAt(this.view.indexOf(item)); + this.startEdit(target, record.data[this.dataIndex]); + this.activeRecord = record; + }else{ + e.preventDefault(); + } + }, + + onSave : function(ed, value){ + this.activeRecord.set(this.dataIndex, value); + } +}); + + +Ext.DataView.DragSelector = function(cfg){ + cfg = cfg || {}; + var view, proxy, tracker; + var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0); + var dragSafe = cfg.dragSafe === true; + + this.init = function(dataView){ + view = dataView; + view.on('render', onRender); + }; + + function fillRegions(){ + rs = []; + view.all.each(function(el){ + rs[rs.length] = el.getRegion(); + }); + bodyRegion = view.el.getRegion(); + } + + function cancelClick(){ + return false; + } + + function onBeforeStart(e){ + return !dragSafe || e.target == view.el.dom; + } + + function onStart(e){ + view.on('containerclick', cancelClick, view, {single:true}); + if(!proxy){ + proxy = view.el.createChild({cls:'x-view-selector'}); + }else{ + proxy.setDisplayed('block'); + } + fillRegions(); + view.clearSelections(); + } + + function onDrag(e){ + var startXY = tracker.startXY; + var xy = tracker.getXY(); + + var x = Math.min(startXY[0], xy[0]); + var y = Math.min(startXY[1], xy[1]); + var w = Math.abs(startXY[0] - xy[0]); + var h = Math.abs(startXY[1] - xy[1]); + + dragRegion.left = x; + dragRegion.top = y; + dragRegion.right = x+w; + dragRegion.bottom = y+h; + + dragRegion.constrainTo(bodyRegion); + proxy.setRegion(dragRegion); + + for(var i = 0, len = rs.length; i < len; i++){ + var r = rs[i], sel = dragRegion.intersect(r); + if(sel && !r.selected){ + r.selected = true; + view.select(i, true); + }else if(!sel && r.selected){ + r.selected = false; + view.deselect(i); + } + } + } + + function onEnd(e){ + if (!Ext.isIE) { + view.un('containerclick', cancelClick, view); + } + if(proxy){ + proxy.setDisplayed(false); + } + } + + function onRender(view){ + tracker = new Ext.dd.DragTracker({ + onBeforeStart: onBeforeStart, + onStart: onStart, + onDrag: onDrag, + onEnd: onEnd + }); + tracker.initEl(view.el); + } +};Ext.ns('Ext.ux.form'); + +/** + * @class Ext.ux.form.FileUploadField + * @extends Ext.form.TextField + * Creates a file upload field. + * @xtype fileuploadfield + */ +Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField, { + /** + * @cfg {String} buttonText The button text to display on the upload button (defaults to + * 'Browse...'). Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text + * value will be used instead if available. + */ + buttonText: 'Browse...', + /** + * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible + * text field (defaults to false). If true, all inherited TextField members will still be available. + */ + buttonOnly: false, + /** + * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field + * (defaults to 3). Note that this only applies if {@link #buttonOnly} = false. + */ + buttonOffset: 3, + /** + * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object. + */ + + // private + readOnly: true, + + /** + * @hide + * @method autoSize + */ + autoSize: Ext.emptyFn, + + // private + initComponent: function(){ + Ext.ux.form.FileUploadField.superclass.initComponent.call(this); + + this.addEvents( + /** + * @event fileselected + * Fires when the underlying file input field's value has changed from the user + * selecting a new file from the system file selection dialog. + * @param {Ext.ux.form.FileUploadField} this + * @param {String} value The file value returned by the underlying file input field + */ + 'fileselected' + ); + }, + + // private + onRender : function(ct, position){ + Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position); + + this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'}); + this.el.addClass('x-form-file-text'); + this.el.dom.removeAttribute('name'); + + this.fileInput = this.wrap.createChild({ + id: this.getFileInputId(), + name: this.name||this.getId(), + cls: 'x-form-file', + tag: 'input', + type: 'file', + size: 1 + }); + + var btnCfg = Ext.applyIf(this.buttonCfg || {}, { + text: this.buttonText + }); + this.button = new Ext.Button(Ext.apply(btnCfg, { + renderTo: this.wrap, + cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '') + })); + + if(this.buttonOnly){ + this.el.hide(); + this.wrap.setWidth(this.button.getEl().getWidth()); + } + + this.fileInput.on('change', function(){ + var v = this.fileInput.dom.value; + this.setValue(v); + this.fireEvent('fileselected', this, v); + }, this); + }, + + // private + getFileInputId: function(){ + return this.id + '-file'; + }, + + // private + onResize : function(w, h){ + Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h); + + this.wrap.setWidth(w); + + if(!this.buttonOnly){ + var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset; + this.el.setWidth(w); + } + }, + + // private + onDestroy: function(){ + Ext.ux.form.FileUploadField.superclass.onDestroy.call(this); + Ext.destroy(this.fileInput, this.button, this.wrap); + }, + + + // private + preFocus : Ext.emptyFn, + + // private + getResizeEl : function(){ + return this.wrap; + }, + + // private + getPositionEl : function(){ + return this.wrap; + }, + + // private + alignErrorIcon : function(){ + this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); + } + +}); + +Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField); + +// backwards compat +Ext.form.FileUploadField = Ext.ux.form.FileUploadField; +(function(){ +Ext.ns('Ext.a11y'); + +Ext.a11y.Frame = Ext.extend(Object, { + initialized: false, + + constructor: function(size, color){ + this.setSize(size || 1); + this.setColor(color || '15428B'); + }, + + init: function(){ + if (!this.initialized) { + this.sides = []; + + var s, i; + + this.ct = Ext.DomHelper.append(document.body, { + cls: 'x-a11y-focusframe' + }, true); + + for (i = 0; i < 4; i++) { + s = Ext.DomHelper.append(this.ct, { + cls: 'x-a11y-focusframe-side', + style: 'background-color: #' + this.color + }, true); + s.visibilityMode = Ext.Element.DISPLAY; + this.sides.push(s); + } + + this.frameTask = new Ext.util.DelayedTask(function(el){ + var newEl = Ext.get(el); + if (newEl != this.curEl) { + var w = newEl.getWidth(); + var h = newEl.getHeight(); + this.sides[0].show().setSize(w, this.size).anchorTo(el, 'tl', [0, -1]); + this.sides[2].show().setSize(w, this.size).anchorTo(el, 'bl', [0, -1]); + this.sides[1].show().setSize(this.size, h).anchorTo(el, 'tr', [-1, 0]); + this.sides[3].show().setSize(this.size, h).anchorTo(el, 'tl', [-1, 0]); + this.curEl = newEl; + } + }, this); + + this.unframeTask = new Ext.util.DelayedTask(function(){ + if (this.initialized) { + this.sides[0].hide(); + this.sides[1].hide(); + this.sides[2].hide(); + this.sides[3].hide(); + this.curEl = null; + } + }, this); + this.initialized = true; + } + }, + + frame: function(el){ + this.init(); + this.unframeTask.cancel(); + this.frameTask.delay(2, false, false, [el]); + }, + + unframe: function(){ + this.init(); + this.unframeTask.delay(2); + }, + + setSize: function(size){ + this.size = size; + }, + + setColor: function(color){ + this.color = color; + } +}); + +Ext.a11y.FocusFrame = new Ext.a11y.Frame(2, '15428B'); +Ext.a11y.RelayFrame = new Ext.a11y.Frame(1, '6B8CBF'); + +Ext.a11y.Focusable = Ext.extend(Ext.util.Observable, { + constructor: function(el, relayTo, noFrame, frameEl){ + Ext.a11y.Focusable.superclass.constructor.call(this); + + this.addEvents('focus', 'blur', 'left', 'right', 'up', 'down', 'esc', 'enter', 'space'); + + if (el instanceof Ext.Component) { + this.el = el.el; + this.setComponent(el); + } + else { + this.el = Ext.get(el); + this.setComponent(null); + } + + this.setRelayTo(relayTo) + this.setNoFrame(noFrame); + this.setFrameEl(frameEl); + + this.init(); + + Ext.a11y.FocusMgr.register(this); + }, + + init: function(){ + this.el.dom.tabIndex = '1'; + this.el.addClass('x-a11y-focusable'); + this.el.on({ + focus: this.onFocus, + blur: this.onBlur, + keydown: this.onKeyDown, + scope: this + }); + }, + + setRelayTo: function(relayTo){ + this.relayTo = relayTo ? Ext.a11y.FocusMgr.get(relayTo) : null; + }, + + setNoFrame: function(noFrame){ + this.noFrame = (noFrame === true) ? true : false; + }, + + setFrameEl: function(frameEl){ + this.frameEl = frameEl && Ext.get(frameEl) || this.el; + }, + + setComponent: function(cmp){ + this.component = cmp || null; + }, + + onKeyDown: function(e, t){ + var k = e.getKey(), SK = Ext.a11y.Focusable.SpecialKeys, ret, tf; + + tf = (t !== this.el.dom) ? Ext.a11y.FocusMgr.get(t, true) : this; + if (!tf) { + // this can happen when you are on a focused item within a panel body + // that is not a Ext.a11y.Focusable + tf = Ext.a11y.FocusMgr.get(Ext.fly(t).parent('.x-a11y-focusable')); + } + + if (SK[k] !== undefined) { + ret = this.fireEvent(SK[k], e, t, tf, this); + } + if (ret === false || this.fireEvent('keydown', e, t, tf, this) === false) { + e.stopEvent(); + } + }, + + focus: function(){ + this.el.dom.focus(); + }, + + blur: function(){ + this.el.dom.blur(); + }, + + onFocus: function(e, t){ + this.el.addClass('x-a11y-focused'); + if (this.relayTo) { + this.relayTo.el.addClass('x-a11y-focused-relay'); + if (!this.relayTo.noFrame) { + Ext.a11y.FocusFrame.frame(this.relayTo.frameEl); + } + if (!this.noFrame) { + Ext.a11y.RelayFrame.frame(this.frameEl); + } + } + else { + if (!this.noFrame) { + Ext.a11y.FocusFrame.frame(this.frameEl); + } + } + + this.fireEvent('focus', e, t, this); + }, + + onBlur: function(e, t){ + if (this.relayTo) { + this.relayTo.el.removeClass('x-a11y-focused-relay'); + Ext.a11y.RelayFrame.unframe(); + } + this.el.removeClass('x-a11y-focused'); + Ext.a11y.FocusFrame.unframe(); + this.fireEvent('blur', e, t, this); + }, + + destroy: function(){ + this.el.un('keydown', this.onKeyDown); + this.el.un('focus', this.onFocus); + this.el.un('blur', this.onBlur); + this.el.removeClass('x-a11y-focusable'); + this.el.removeClass('x-a11y-focused'); + if (this.relayTo) { + this.relayTo.el.removeClass('x-a11y-focused-relay'); + } + } +}); + +Ext.a11y.FocusItem = Ext.extend(Object, { + constructor: function(el, enableTabbing){ + Ext.a11y.FocusItem.superclass.constructor.call(this); + + this.el = Ext.get(el); + this.fi = new Ext.a11y.Focusable(el); + this.fi.setComponent(this); + + this.fi.on('tab', this.onTab, this); + + this.enableTabbing = enableTabbing === true ? true : false; + }, + + getEnterItem: function(){ + if (this.enableTabbing) { + var items = this.getFocusItems(); + if (items && items.length) { + return items[0]; + } + } + }, + + getFocusItems: function(){ + if (this.enableTabbing) { + return this.el.query('a, button, input, select'); + } + return null; + }, + + onTab: function(e, t){ + var items = this.getFocusItems(), i; + + if (items && items.length && (i = items.indexOf(t)) !== -1) { + if (e.shiftKey && i > 0) { + e.stopEvent(); + items[i - 1].focus(); + Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]); + return; + } + else + if (!e.shiftKey && i < items.length - 1) { + e.stopEvent(); + items[i + 1].focus(); + Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]); + return; + } + } + }, + + focus: function(){ + if (this.enableTabbing) { + var items = this.getFocusItems(); + if (items && items.length) { + items[0].focus(); + Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]); + return; + } + } + this.fi.focus(); + }, + + blur: function(){ + this.fi.blur(); + } +}); + +Ext.a11y.FocusMgr = function(){ + var all = new Ext.util.MixedCollection(); + + return { + register: function(f){ + all.add(f.el && Ext.id(f.el), f); + }, + + unregister: function(f){ + all.remove(f); + }, + + get: function(el, noCreate){ + return all.get(Ext.id(el)) || (noCreate ? false : new Ext.a11y.Focusable(el)); + }, + + all: all + } +}(); + +Ext.a11y.Focusable.SpecialKeys = {}; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.LEFT] = 'left'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.RIGHT] = 'right'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.DOWN] = 'down'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.UP] = 'up'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ESC] = 'esc'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ENTER] = 'enter'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.SPACE] = 'space'; +Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.TAB] = 'tab'; + +// we use the new observeClass method to fire our new initFocus method on components +Ext.util.Observable.observeClass(Ext.Component); +Ext.Component.on('render', function(cmp){ + cmp.initFocus(); + cmp.initARIA(); +}); +Ext.override(Ext.Component, { + initFocus: Ext.emptyFn, + initARIA: Ext.emptyFn +}); + +Ext.override(Ext.Container, { + isFocusable: true, + noFocus: false, + + // private + initFocus: function(){ + if (!this.fi && !this.noFocus) { + this.fi = new Ext.a11y.Focusable(this); + } + this.mon(this.fi, { + focus: this.onFocus, + blur: this.onBlur, + tab: this.onTab, + enter: this.onEnter, + esc: this.onEsc, + scope: this + }); + + if (this.hidden) { + this.isFocusable = false; + } + + this.on('show', function(){ + this.isFocusable = true; + }, this); + this.on('hide', function(){ + this.isFocusable = false; + }, this); + }, + + focus: function(){ + this.fi.focus(); + }, + + blur: function(){ + this.fi.blur(); + }, + + enter: function(){ + var eitem = this.getEnterItem(); + if (eitem) { + eitem.focus(); + } + }, + + onFocus: Ext.emptyFn, + onBlur: Ext.emptyFn, + + onTab: function(e, t, tf){ + var rf = tf.relayTo || tf; + if (rf.component && rf.component !== this) { + e.stopEvent(); + var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component); + item.focus(); + } + }, + + onEnter: function(e, t, tf){ + // check to see if enter is pressed while "on" the panel + if (tf.component && tf.component === this) { + e.stopEvent(); + this.enter(); + } + e.stopPropagation(); + }, + + onEsc: function(e, t){ + e.preventDefault(); + + // check to see if esc is pressed while "inside" the panel + // or while "on" the panel + if (t === this.el.dom) { + // "on" the panel, check if this panel has an owner panel and focus that + // we dont stop the event in this case so that this same check will be + // done for this ownerCt + if (this.ownerCt) { + this.ownerCt.focus(); + } + } + else { + // we were inside the panel when esc was pressed, + // so go back "on" the panel + if (this.ownerCt && this.ownerCt.isFocusable) { + var si = this.ownerCt.getFocusItems(); + + if (si && si.getCount() > 1) { + e.stopEvent(); + } + } + this.focus(); + } + }, + + getFocusItems: function(){ + return this.items && + this.items.filterBy(function(o){ + return o.isFocusable; + }) || + null; + }, + + getEnterItem: function(){ + var ci = this.getFocusItems(), length = ci ? ci.getCount() : 0; + + if (length === 1) { + return ci.first().getEnterItem && ci.first().getEnterItem() || ci.first(); + } + else + if (length > 1) { + return ci.first(); + } + }, + + getNextFocus: function(current){ + var items = this.getFocusItems(), next = current, i = items.indexOf(current), length = items.getCount(); + + if (i === length - 1) { + next = items.first(); + } + else { + next = items.get(i + 1); + } + return next; + }, + + getPreviousFocus: function(current){ + var items = this.getFocusItems(), prev = current, i = items.indexOf(current), length = items.getCount(); + + if (i === 0) { + prev = items.last(); + } + else { + prev = items.get(i - 1); + } + return prev; + }, + + getFocusable : function() { + return this.fi; + } +}); + +Ext.override(Ext.Panel, { + /** + * @cfg {Boolean} enableTabbing true to enable tabbing. Default is false. + */ + getFocusItems: function(){ + // items gets all the items inside the body + var items = Ext.Panel.superclass.getFocusItems.call(this), bodyFocus = null; + + if (!items) { + items = new Ext.util.MixedCollection(); + this.bodyFocus = this.bodyFocus || new Ext.a11y.FocusItem(this.body, this.enableTabbing); + items.add('body', this.bodyFocus); + } + // but panels can also have tbar, bbar, fbar + if (this.tbar && this.topToolbar) { + items.insert(0, this.topToolbar); + } + if (this.bbar && this.bottomToolbar) { + items.add(this.bottomToolbar); + } + if (this.fbar) { + items.add(this.fbar); + } + + return items; + } +}); + +Ext.override(Ext.TabPanel, { + // private + initFocus: function(){ + Ext.TabPanel.superclass.initFocus.call(this); + this.mon(this.fi, { + left: this.onLeft, + right: this.onRight, + scope: this + }); + }, + + onLeft: function(e){ + if (!this.activeTab) { + return; + } + e.stopEvent(); + var prev = this.items.itemAt(this.items.indexOf(this.activeTab) - 1); + if (prev) { + this.setActiveTab(prev); + } + return false; + }, + + onRight: function(e){ + if (!this.activeTab) { + return; + } + e.stopEvent(); + var next = this.items.itemAt(this.items.indexOf(this.activeTab) + 1); + if (next) { + this.setActiveTab(next); + } + return false; + } +}); + +Ext.override(Ext.tree.TreeNodeUI, { + // private + focus: function(){ + this.node.getOwnerTree().bodyFocus.focus(); + } +}); + +Ext.override(Ext.tree.TreePanel, { + // private + afterRender : function(){ + Ext.tree.TreePanel.superclass.afterRender.call(this); + this.root.render(); + if(!this.rootVisible){ + this.root.renderChildren(); + } + this.bodyFocus = new Ext.a11y.FocusItem(this.body.down('.x-tree-root-ct')); + this.bodyFocus.fi.setFrameEl(this.body); + } +}); + +Ext.override(Ext.grid.GridPanel, { + initFocus: function(){ + Ext.grid.GridPanel.superclass.initFocus.call(this); + this.bodyFocus = new Ext.a11y.FocusItem(this.view.focusEl); + this.bodyFocus.fi.setFrameEl(this.body); + } +}); + +Ext.override(Ext.Button, { + isFocusable: true, + noFocus: false, + + initFocus: function(){ + Ext.Button.superclass.initFocus.call(this); + this.fi = this.fi || new Ext.a11y.Focusable(this.btnEl, null, null, this.el); + this.fi.setComponent(this); + + this.mon(this.fi, { + focus: this.onFocus, + blur: this.onBlur, + scope: this + }); + + if (this.menu) { + this.mon(this.fi, 'down', this.showMenu, this); + this.on('menuhide', this.focus, this); + } + + if (this.hidden) { + this.isFocusable = false; + } + + this.on('show', function(){ + this.isFocusable = true; + }, this); + this.on('hide', function(){ + this.isFocusable = false; + }, this); + }, + + focus: function(){ + this.fi.focus(); + }, + + blur: function(){ + this.fi.blur(); + }, + + onFocus: function(){ + if (!this.disabled) { + this.el.addClass("x-btn-focus"); + } + }, + + onBlur: function(){ + this.el.removeClass("x-btn-focus"); + } +}); + +Ext.override(Ext.Toolbar, { + initFocus: function(){ + Ext.Toolbar.superclass.initFocus.call(this); + this.mon(this.fi, { + left: this.onLeft, + right: this.onRight, + scope: this + }); + + this.on('focus', this.onButtonFocus, this, { + stopEvent: true + }); + }, + + addItem: function(item){ + Ext.Toolbar.superclass.add.apply(this, arguments); + if (item.rendered && item.fi !== undefined) { + item.fi.setRelayTo(this.el); + this.relayEvents(item.fi, ['focus']); + } + else { + item.on('render', function(){ + if (item.fi !== undefined) { + item.fi.setRelayTo(this.el); + this.relayEvents(item.fi, ['focus']); + } + }, this, { + single: true + }); + } + return item; + }, + + onFocus: function(){ + var items = this.getFocusItems(); + if (items && items.getCount() > 0) { + if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) { + this.lastFocus.focus(); + } + else { + items.first().focus(); + } + } + }, + + onButtonFocus: function(e, t, tf){ + this.lastFocus = tf.component || null; + }, + + onLeft: function(e, t, tf){ + e.stopEvent(); + this.getPreviousFocus(tf.component).focus(); + }, + + onRight: function(e, t, tf){ + e.stopEvent(); + this.getNextFocus(tf.component).focus(); + }, + + getEnterItem: Ext.emptyFn, + onTab: Ext.emptyFn, + onEsc: Ext.emptyFn +}); + +Ext.override(Ext.menu.BaseItem, { + initFocus: function(){ + this.fi = new Ext.a11y.Focusable(this, this.parentMenu && this.parentMenu.el || null, true); + } +}); + +Ext.override(Ext.menu.Menu, { + initFocus: function(){ + this.fi = new Ext.a11y.Focusable(this); + this.focusEl = this.fi; + } +}); + +Ext.a11y.WindowMgr = new Ext.WindowGroup(); + +Ext.apply(Ext.WindowMgr, { + bringToFront: function(win){ + Ext.a11y.WindowMgr.bringToFront.call(this, win); + if (win.modal) { + win.enter(); + } + else { + win.focus(); + } + } +}); + +Ext.override(Ext.Window, { + initFocus: function(){ + Ext.Window.superclass.initFocus.call(this); + this.on('beforehide', function(){ + Ext.a11y.RelayFrame.unframe(); + Ext.a11y.FocusFrame.unframe(); + }); + } +}); + +Ext.override(Ext.form.Field, { + isFocusable: true, + noFocus: false, + + initFocus: function(){ + this.fi = this.fi || new Ext.a11y.Focusable(this, null, true); + + Ext.form.Field.superclass.initFocus.call(this); + + if (this.hidden) { + this.isFocusable = false; + } + + this.on('show', function(){ + this.isFocusable = true; + }, this); + this.on('hide', function(){ + this.isFocusable = false; + }, this); + } +}); + +Ext.override(Ext.FormPanel, { + initFocus: function(){ + Ext.FormPanel.superclass.initFocus.call(this); + this.on('focus', this.onFieldFocus, this, { + stopEvent: true + }); + }, + + // private + createForm: function(){ + delete this.initialConfig.listeners; + var form = new Ext.form.BasicForm(null, this.initialConfig); + form.afterMethod('add', this.formItemAdd, this); + return form; + }, + + formItemAdd: function(item){ + item.on('render', function(field){ + field.fi.setRelayTo(this.el); + this.relayEvents(field.fi, ['focus']); + }, this, { + single: true + }); + }, + + onFocus: function(){ + var items = this.getFocusItems(); + if (items && items.getCount() > 0) { + if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) { + this.lastFocus.focus(); + } + else { + items.first().focus(); + } + } + }, + + onFieldFocus: function(e, t, tf){ + this.lastFocus = tf.component || null; + }, + + onTab: function(e, t, tf){ + if (tf.relayTo.component === this) { + var item = e.shiftKey ? this.getPreviousFocus(tf.component) : this.getNextFocus(tf.component); + + if (item) { + ev.stopEvent(); + item.focus(); + return; + } + } + Ext.FormPanel.superclass.onTab.apply(this, arguments); + }, + + getNextFocus: function(current){ + var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount(); + + return (i < length - 1) ? items.get(i + 1) : false; + }, + + getPreviousFocus: function(current){ + var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount(); + + return (i > 0) ? items.get(i - 1) : false; + } +}); + +Ext.override(Ext.Viewport, { + initFocus: function(){ + Ext.Viewport.superclass.initFocus.apply(this); + this.mon(Ext.get(document), 'focus', this.focus, this); + this.mon(Ext.get(document), 'blur', this.blur, this); + this.fi.setNoFrame(true); + }, + + onTab: function(e, t, tf, f){ + e.stopEvent(); + + if (tf === f) { + items = this.getFocusItems(); + if (items && items.getCount() > 0) { + items.first().focus(); + } + } + else { + var rf = tf.relayTo || tf; + var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component); + item.focus(); + } + } +}); + +})();/** + * @class Ext.ux.GMapPanel + * @extends Ext.Panel + * @author Shea Frederick + */ +Ext.ux.GMapPanel = Ext.extend(Ext.Panel, { + initComponent : function(){ + + var defConfig = { + plain: true, + zoomLevel: 3, + yaw: 180, + pitch: 0, + zoom: 0, + gmapType: 'map', + border: false + }; + + Ext.applyIf(this,defConfig); + + Ext.ux.GMapPanel.superclass.initComponent.call(this); + + }, + afterRender : function(){ + + var wh = this.ownerCt.getSize(); + Ext.applyIf(this, wh); + + Ext.ux.GMapPanel.superclass.afterRender.call(this); + + if (this.gmapType === 'map'){ + this.gmap = new GMap2(this.body.dom); + } + + if (this.gmapType === 'panorama'){ + this.gmap = new GStreetviewPanorama(this.body.dom); + } + + if (typeof this.addControl == 'object' && this.gmapType === 'map') { + this.gmap.addControl(this.addControl); + } + + if (typeof this.setCenter === 'object') { + if (typeof this.setCenter.geoCodeAddr === 'string'){ + this.geoCodeLookup(this.setCenter.geoCodeAddr); + }else{ + if (this.gmapType === 'map'){ + var point = new GLatLng(this.setCenter.lat,this.setCenter.lng); + this.gmap.setCenter(point, this.zoomLevel); + } + if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){ + this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear); + } + } + if (this.gmapType === 'panorama'){ + this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom}); + } + } + + GEvent.bind(this.gmap, 'load', this, function(){ + this.onMapReady(); + }); + + }, + onMapReady : function(){ + this.addMarkers(this.markers); + this.addMapControls(); + this.addOptions(); + }, + onResize : function(w, h){ + + if (typeof this.getMap() == 'object') { + this.gmap.checkResize(); + } + + Ext.ux.GMapPanel.superclass.onResize.call(this, w, h); + + }, + setSize : function(width, height, animate){ + + if (typeof this.getMap() == 'object') { + this.gmap.checkResize(); + } + + Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate); + + }, + getMap : function(){ + + return this.gmap; + + }, + getCenter : function(){ + + return this.getMap().getCenter(); + + }, + getCenterLatLng : function(){ + + var ll = this.getCenter(); + return {lat: ll.lat(), lng: ll.lng()}; + + }, + addMarkers : function(markers) { + + if (Ext.isArray(markers)){ + for (var i = 0; i < markers.length; i++) { + var mkr_point = new GLatLng(markers[i].lat,markers[i].lng); + this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners); + } + } + + }, + addMarker : function(point, marker, clear, center, listeners){ + + Ext.applyIf(marker,G_DEFAULT_ICON); + + if (clear === true){ + this.getMap().clearOverlays(); + } + if (center === true) { + this.getMap().setCenter(point, this.zoomLevel); + } + + var mark = new GMarker(point,marker); + if (typeof listeners === 'object'){ + for (evt in listeners) { + GEvent.bind(mark, evt, this, listeners[evt]); + } + } + this.getMap().addOverlay(mark); + + }, + addMapControls : function(){ + + if (this.gmapType === 'map') { + if (Ext.isArray(this.mapControls)) { + for(i=0;i
    Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)'); + }else{ + point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]); + if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){ + this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners); + } + } + } + } + + } + +}); + +Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.ns('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.GroupSummary + * @extends Ext.util.Observable + * A GridPanel plugin that enables dynamic column calculations and a dynamically + * updated grouped summary row. + */ +Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, { + /** + * @cfg {Function} summaryRenderer Renderer example:
    
    +summaryRenderer: function(v, params, data){
    +    return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
    +},
    +     * 
    + */ + /** + * @cfg {String} summaryType (Optional) The type of + * calculation to be used for the column. For options available see + * {@link #Calculations}. + */ + + constructor : function(config){ + Ext.apply(this, config); + Ext.ux.grid.GroupSummary.superclass.constructor.call(this); + }, + init : function(grid){ + this.grid = grid; + this.cm = grid.getColumnModel(); + this.view = grid.getView(); + + var v = this.view; + v.doGroupEnd = this.doGroupEnd.createDelegate(this); + + v.afterMethod('onColumnWidthUpdated', this.doWidth, this); + v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this); + v.afterMethod('onColumnHiddenUpdated', this.doHidden, this); + v.afterMethod('onUpdate', this.doUpdate, this); + v.afterMethod('onRemove', this.doRemove, this); + + if(!this.rowTpl){ + this.rowTpl = new Ext.Template( + '
    ', + '', + '{cells}', + '
    ' + ); + this.rowTpl.disableFormats = true; + } + this.rowTpl.compile(); + + if(!this.cellTpl){ + this.cellTpl = new Ext.Template( + '', + '
    {value}
    ', + "" + ); + this.cellTpl.disableFormats = true; + } + this.cellTpl.compile(); + }, + + /** + * Toggle the display of the summary row on/off + * @param {Boolean} visible true to show the summary, false to hide the summary. + */ + toggleSummaries : function(visible){ + var el = this.grid.getGridEl(); + if(el){ + if(visible === undefined){ + visible = el.hasClass('x-grid-hide-summary'); + } + el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary'); + } + }, + + renderSummary : function(o, cs){ + cs = cs || this.view.getColumnData(); + var cfg = this.cm.config; + + var buf = [], c, p = {}, cf, last = cs.length-1; + for(var i = 0, len = cs.length; i < len; i++){ + c = cs[i]; + cf = cfg[i]; + p.id = c.id; + p.style = c.style; + p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : ''); + if(cf.summaryType || cf.summaryRenderer){ + p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o); + }else{ + p.value = ''; + } + if(p.value == undefined || p.value === "") p.value = " "; + buf[buf.length] = this.cellTpl.apply(p); + } + + return this.rowTpl.apply({ + tstyle: 'width:'+this.view.getTotalWidth()+';', + cells: buf.join('') + }); + }, + + /** + * @private + * @param {Object} rs + * @param {Object} cs + */ + calculate : function(rs, cs){ + var data = {}, r, c, cfg = this.cm.config, cf; + for(var j = 0, jlen = rs.length; j < jlen; j++){ + r = rs[j]; + for(var i = 0, len = cs.length; i < len; i++){ + c = cs[i]; + cf = cfg[i]; + if(cf.summaryType){ + data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data); + } + } + } + return data; + }, + + doGroupEnd : function(buf, g, cs, ds, colCount){ + var data = this.calculate(g.rs, cs); + buf.push('', this.renderSummary({data: data}, cs), ''); + }, + + doWidth : function(col, w, tw){ + var gs = this.view.getGroups(), s; + for(var i = 0, len = gs.length; i < len; i++){ + s = gs[i].childNodes[2]; + s.style.width = tw; + s.firstChild.style.width = tw; + s.firstChild.rows[0].childNodes[col].style.width = w; + } + }, + + doAllWidths : function(ws, tw){ + var gs = this.view.getGroups(), s, cells, wlen = ws.length; + for(var i = 0, len = gs.length; i < len; i++){ + s = gs[i].childNodes[2]; + s.style.width = tw; + s.firstChild.style.width = tw; + cells = s.firstChild.rows[0].childNodes; + for(var j = 0; j < wlen; j++){ + cells[j].style.width = ws[j]; + } + } + }, + + doHidden : function(col, hidden, tw){ + var gs = this.view.getGroups(), s, display = hidden ? 'none' : ''; + for(var i = 0, len = gs.length; i < len; i++){ + s = gs[i].childNodes[2]; + s.style.width = tw; + s.firstChild.style.width = tw; + s.firstChild.rows[0].childNodes[col].style.display = display; + } + }, + + // Note: requires that all (or the first) record in the + // group share the same group value. Returns false if the group + // could not be found. + refreshSummary : function(groupValue){ + return this.refreshSummaryById(this.view.getGroupId(groupValue)); + }, + + getSummaryNode : function(gid){ + var g = Ext.fly(gid, '_gsummary'); + if(g){ + return g.down('.x-grid3-summary-row', true); + } + return null; + }, + + refreshSummaryById : function(gid){ + var g = document.getElementById(gid); + if(!g){ + return false; + } + var rs = []; + this.grid.store.each(function(r){ + if(r._groupId == gid){ + rs[rs.length] = r; + } + }); + var cs = this.view.getColumnData(); + var data = this.calculate(rs, cs); + var markup = this.renderSummary({data: data}, cs); + + var existing = this.getSummaryNode(gid); + if(existing){ + g.removeChild(existing); + } + Ext.DomHelper.append(g, markup); + return true; + }, + + doUpdate : function(ds, record){ + this.refreshSummaryById(record._groupId); + }, + + doRemove : function(ds, record, index, isUpdate){ + if(!isUpdate){ + this.refreshSummaryById(record._groupId); + } + }, + + /** + * Show a message in the summary row. + *
    
    +grid.on('afteredit', function(){
    +    var groupValue = 'Ext Forms: Field Anchoring';
    +    summary.showSummaryMsg(groupValue, 'Updating Summary...');
    +});
    +     * 
    + * @param {String} groupValue + * @param {String} msg Text to use as innerHTML for the summary row. + */ + showSummaryMsg : function(groupValue, msg){ + var gid = this.view.getGroupId(groupValue); + var node = this.getSummaryNode(gid); + if(node){ + node.innerHTML = '
    ' + msg + '
    '; + } + } +}); + +//backwards compat +Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary; + + +/** + * Calculation types for summary row:

    + *

    Custom calculations may be implemented. An example of + * custom summaryType=totalCost:

    
    +// define a custom summary function
    +Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
    +    return v + (record.data.estimate * record.data.rate);
    +};
    + * 
    + * @property Calculations + */ + +Ext.ux.grid.GroupSummary.Calculations = { + 'sum' : function(v, record, field){ + return v + (record.data[field]||0); + }, + + 'count' : function(v, record, field, data){ + return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1); + }, + + 'max' : function(v, record, field, data){ + var v = record.data[field]; + var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max']; + return v > max ? (data[field+'max'] = v) : max; + }, + + 'min' : function(v, record, field, data){ + var v = record.data[field]; + var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min']; + return v < min ? (data[field+'min'] = v) : min; + }, + + 'average' : function(v, record, field, data){ + var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1); + var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0))); + return t === 0 ? 0 : t / c; + } +}; +Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations; + +/** + * @class Ext.ux.grid.HybridSummary + * @extends Ext.ux.grid.GroupSummary + * Adds capability to specify the summary data for the group via json as illustrated here: + *
    
    +{
    +    data: [
    +        {
    +            projectId: 100,     project: 'House',
    +            taskId:    112, description: 'Paint',
    +            estimate:    6,        rate:     150,
    +            due:'06/24/2007'
    +        },
    +        ...
    +    ],
    +
    +    summaryData: {
    +        'House': {
    +            description: 14, estimate: 9,
    +                   rate: 99, due: new Date(2009, 6, 29),
    +                   cost: 999
    +        }
    +    }
    +}
    + * 
    + * + */ +Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, { + /** + * @private + * @param {Object} rs + * @param {Object} cs + */ + calculate : function(rs, cs){ + var gcol = this.view.getGroupField(); + var gvalue = rs[0].data[gcol]; + var gdata = this.getSummaryData(gvalue); + return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs); + }, + + /** + *
    
    +grid.on('afteredit', function(){
    +    var groupValue = 'Ext Forms: Field Anchoring';
    +    summary.showSummaryMsg(groupValue, 'Updating Summary...');
    +    setTimeout(function(){ // simulate server call
    +        // HybridSummary class implements updateSummaryData
    +        summary.updateSummaryData(groupValue,
    +            // create data object based on configured dataIndex
    +            {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});
    +    }, 2000);
    +});
    +     * 
    + * @param {String} groupValue + * @param {Object} data data object + * @param {Boolean} skipRefresh (Optional) Defaults to false + */ + updateSummaryData : function(groupValue, data, skipRefresh){ + var json = this.grid.store.reader.jsonData; + if(!json.summaryData){ + json.summaryData = {}; + } + json.summaryData[groupValue] = data; + if(!skipRefresh){ + this.refreshSummary(groupValue); + } + }, + + /** + * Returns the summaryData for the specified groupValue or null. + * @param {String} groupValue + * @return {Object} summaryData + */ + getSummaryData : function(groupValue){ + var json = this.grid.store.reader.jsonData; + if(json && json.summaryData){ + return json.summaryData[groupValue]; + } + return null; + } +}); + +//backwards compat +Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary; +Ext.ux.GroupTab = Ext.extend(Ext.Container, { + mainItem: 0, + + expanded: true, + + deferredRender: true, + + activeTab: null, + + idDelimiter: '__', + + headerAsText: false, + + frame: false, + + hideBorders: true, + + initComponent: function(config){ + Ext.apply(this, config); + this.frame = false; + + Ext.ux.GroupTab.superclass.initComponent.call(this); + + this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange'); + + this.setLayout(new Ext.layout.CardLayout({ + deferredRender: this.deferredRender + })); + + if (!this.stack) { + this.stack = Ext.TabPanel.AccessStack(); + } + + this.initItems(); + + this.on('beforerender', function(){ + this.groupEl = this.ownerCt.getGroupEl(this); + }, this); + + this.on('add', this.onAdd, this, { + target: this + }); + this.on('remove', this.onRemove, this, { + target: this + }); + + if (this.mainItem !== undefined) { + var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem); + delete this.mainItem; + this.setMainItem(item); + } + }, + + /** + * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which + * can return false to cancel the tab change. + * @param {String/Panel} tab The id or tab Panel to activate + */ + setActiveTab : function(item){ + item = this.getComponent(item); + if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){ + return; + } + if(!this.rendered){ + this.activeTab = item; + return; + } + if(this.activeTab != item){ + if(this.activeTab && this.activeTab != this.mainItem){ + var oldEl = this.getTabEl(this.activeTab); + if(oldEl){ + Ext.fly(oldEl).removeClass('x-grouptabs-strip-active'); + } + this.activeTab.fireEvent('deactivate', this.activeTab); + } + var el = this.getTabEl(item); + Ext.fly(el).addClass('x-grouptabs-strip-active'); + this.activeTab = item; + this.stack.add(item); + + this.layout.setActiveItem(item); + if(this.layoutOnTabChange && item.doLayout){ + item.doLayout(); + } + if(this.scrolling){ + this.scrollToTab(item, this.animScroll); + } + + item.fireEvent('activate', item); + this.fireEvent('tabchange', this, item); + } + }, + + getTabEl: function(item){ + if (item == this.mainItem) { + return this.groupEl; + } + return Ext.TabPanel.prototype.getTabEl.call(this, item); + }, + + onRender: function(ct, position){ + Ext.ux.GroupTab.superclass.onRender.call(this, ct, position); + + this.strip = Ext.fly(this.groupEl).createChild({ + tag: 'ul', + cls: 'x-grouptabs-sub' + }); + + this.tooltip = new Ext.ToolTip({ + target: this.groupEl, + delegate: 'a.x-grouptabs-text', + trackMouse: true, + renderTo: document.body, + listeners: { + beforeshow: function(tip) { + var item = (tip.triggerElement.parentNode === this.mainItem.tabEl) + ? this.mainItem + : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]); + + if(!item.tabTip) { + return false; + } + tip.body.dom.innerHTML = item.tabTip; + }, + scope: this + } + }); + + if (!this.itemTpl) { + var tt = new Ext.Template('
  • ', '{text}', '
  • '); + tt.disableFormats = true; + tt.compile(); + Ext.ux.GroupTab.prototype.itemTpl = tt; + } + + this.items.each(this.initTab, this); + }, + + afterRender: function(){ + Ext.ux.GroupTab.superclass.afterRender.call(this); + + if (this.activeTab !== undefined) { + var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab); + delete this.activeTab; + this.setActiveTab(item); + } + }, + + // private + initTab: function(item, index){ + var before = this.strip.dom.childNodes[index]; + var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item); + + if (item === this.mainItem) { + item.tabEl = this.groupEl; + p.cls += ' x-grouptabs-main-item'; + } + + var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p); + + item.tabEl = item.tabEl || el; + + item.on('disable', this.onItemDisabled, this); + item.on('enable', this.onItemEnabled, this); + item.on('titlechange', this.onItemTitleChanged, this); + item.on('iconchange', this.onItemIconChanged, this); + item.on('beforeshow', this.onBeforeShowItem, this); + }, + + setMainItem: function(item){ + item = this.getComponent(item); + if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) { + return; + } + + this.mainItem = item; + }, + + getMainItem: function(){ + return this.mainItem || null; + }, + + // private + onBeforeShowItem: function(item){ + if (item != this.activeTab) { + this.setActiveTab(item); + return false; + } + }, + + // private + onAdd: function(gt, item, index){ + if (this.rendered) { + this.initTab.call(this, item, index); + } + }, + + // private + onRemove: function(tp, item){ + Ext.destroy(Ext.get(this.getTabEl(item))); + this.stack.remove(item); + item.un('disable', this.onItemDisabled, this); + item.un('enable', this.onItemEnabled, this); + item.un('titlechange', this.onItemTitleChanged, this); + item.un('iconchange', this.onItemIconChanged, this); + item.un('beforeshow', this.onBeforeShowItem, this); + if (item == this.activeTab) { + var next = this.stack.next(); + if (next) { + this.setActiveTab(next); + } + else if (this.items.getCount() > 0) { + this.setActiveTab(0); + } + else { + this.activeTab = null; + } + } + }, + + // private + onBeforeAdd: function(item){ + var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item); + if (existing) { + this.setActiveTab(item); + return false; + } + Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments); + var es = item.elements; + item.elements = es ? es.replace(',header', '') : es; + item.border = (item.border === true); + }, + + // private + onItemDisabled: Ext.TabPanel.prototype.onItemDisabled, + onItemEnabled: Ext.TabPanel.prototype.onItemEnabled, + + // private + onItemTitleChanged: function(item){ + var el = this.getTabEl(item); + if (el) { + Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title; + } + }, + + //private + onItemIconChanged: function(item, iconCls, oldCls){ + var el = this.getTabEl(item); + if (el) { + Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls); + } + }, + + beforeDestroy: function(){ + Ext.TabPanel.prototype.beforeDestroy.call(this); + this.tooltip.destroy(); + } +}); + +Ext.reg('grouptab', Ext.ux.GroupTab); +Ext.ns('Ext.ux'); + +Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, { + tabPosition: 'left', + + alternateColor: false, + + alternateCls: 'x-grouptabs-panel-alt', + + defaultType: 'grouptab', + + deferredRender: false, + + activeGroup : null, + + initComponent: function(){ + Ext.ux.GroupTabPanel.superclass.initComponent.call(this); + + this.addEvents( + 'beforegroupchange', + 'groupchange' + ); + this.elements = 'body,header'; + this.stripTarget = 'header'; + + this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left'; + + this.addClass('x-grouptabs-panel'); + + if (this.tabStyle && this.tabStyle != '') { + this.addClass('x-grouptabs-panel-' + this.tabStyle); + } + + if (this.alternateColor) { + this.addClass(this.alternateCls); + } + + this.on('beforeadd', function(gtp, item, index){ + this.initGroup(item, index); + }); + }, + + initEvents : function() { + this.mon(this.strip, 'mousedown', this.onStripMouseDown, this); + }, + + onRender: function(ct, position){ + Ext.TabPanel.superclass.onRender.call(this, ct, position); + + if(this.plain){ + var pos = this.tabPosition == 'top' ? 'header' : 'footer'; + this[pos].addClass('x-tab-panel-'+pos+'-plain'); + } + + var st = this[this.stripTarget]; + + this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{ + tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}}); + + var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null); + this.strip = new Ext.Element(this.stripWrap.dom.firstChild); + + this.header.addClass('x-grouptabs-panel-header'); + this.bwrap.addClass('x-grouptabs-bwrap'); + this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body'); + + if (!this.itemTpl) { + var tt = new Ext.Template( + '
  • ', + '', + '', + '{text}', + '
  • ' + ); + tt.disableFormats = true; + tt.compile(); + Ext.ux.GroupTabPanel.prototype.itemTpl = tt; + } + + this.items.each(this.initGroup, this); + }, + + afterRender: function(){ + Ext.ux.GroupTabPanel.superclass.afterRender.call(this); + + this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({ + cls: 'x-tab-joint' + }); + + this.addClass('x-tab-panel-' + this.tabPosition); + this.header.setWidth(this.tabWidth); + + if (this.activeGroup !== undefined) { + var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup); + delete this.activeGroup; + this.setActiveGroup(group); + group.setActiveTab(group.getMainItem()); + } + }, + + getGroupEl : Ext.TabPanel.prototype.getTabEl, + + // private + findTargets: function(e){ + var item = null; + var itemEl = e.getTarget('li', this.strip); + if (itemEl) { + item = this.findById(itemEl.id.split(this.idDelimiter)[1]); + if (item.disabled) { + return { + expand: null, + item: null, + el: null + }; + } + } + return { + expand: e.getTarget('.x-grouptabs-expand', this.strip), + isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip), + item: item, + el: itemEl + }; + }, + + // private + onStripMouseDown: function(e){ + if (e.button != 0) { + return; + } + e.preventDefault(); + var t = this.findTargets(e); + if (t.expand) { + this.toggleGroup(t.el); + } + else if (t.item) { + if(t.isGroup) { + t.item.setActiveTab(t.item.getMainItem()); + } + else { + t.item.ownerCt.setActiveTab(t.item); + } + } + }, + + expandGroup: function(groupEl){ + if(groupEl.isXType) { + groupEl = this.getGroupEl(groupEl); + } + Ext.fly(groupEl).addClass('x-grouptabs-expanded'); + }, + + toggleGroup: function(groupEl){ + if(groupEl.isXType) { + groupEl = this.getGroupEl(groupEl); + } + Ext.fly(groupEl).toggleClass('x-grouptabs-expanded'); + this.syncTabJoint(); + }, + + syncTabJoint: function(groupEl){ + if (!this.tabJoint) { + return; + } + + groupEl = groupEl || this.getGroupEl(this.activeGroup); + if(groupEl) { + this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); + + var y = Ext.isGecko2 ? 0 : 1; + if (this.tabPosition == 'left'){ + this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]); + } + else { + this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]); + } + } + else { + this.tabJoint.hide(); + } + }, + + getActiveTab : function() { + if(!this.activeGroup) return null; + return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null; + }, + + onResize: function(){ + Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments); + this.syncTabJoint(); + }, + + createCorner: function(el, pos){ + return Ext.fly(el).createChild({ + cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos + }); + }, + + initGroup: function(group, index){ + var before = this.strip.dom.childNodes[index]; + var p = this.getTemplateArgs(group); + if (index === 0) { + p.cls += ' x-tab-first'; + } + p.cls += ' x-grouptabs-main'; + p.text = group.getMainItem().title; + + var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p); + + var tl = this.createCorner(el, 'top-' + this.tabPosition); + var bl = this.createCorner(el, 'bottom-' + this.tabPosition); + + if (group.expanded) { + this.expandGroup(el); + } + + if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){ + bl.setLeft('-10px'); + bl.setBottom('-5px'); + tl.setLeft('-10px'); + tl.setTop('-5px'); + } + + this.mon(group, 'changemainitem', this.onGroupChangeMainItem, this); + this.mon(group, 'beforetabchange', this.onGroupBeforeTabChange, this); + }, + + setActiveGroup : function(group) { + group = this.getComponent(group); + if(!group || this.fireEvent('beforegroupchange', this, group, this.activeGroup) === false){ + return; + } + if(!this.rendered){ + this.activeGroup = group; + return; + } + if(this.activeGroup != group){ + if(this.activeGroup){ + var oldEl = this.getGroupEl(this.activeGroup); + if(oldEl){ + Ext.fly(oldEl).removeClass('x-grouptabs-strip-active'); + } + this.activeGroup.fireEvent('deactivate', this.activeTab); + } + + var groupEl = this.getGroupEl(group); + Ext.fly(groupEl).addClass('x-grouptabs-strip-active'); + + this.activeGroup = group; + this.stack.add(group); + + this.layout.setActiveItem(group); + this.syncTabJoint(groupEl); + + group.fireEvent('activate', group); + this.fireEvent('groupchange', this, group); + } + }, + + onGroupBeforeTabChange: function(group, newTab, oldTab){ + if(group !== this.activeGroup || newTab !== oldTab) { + this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active'); + } + + this.expandGroup(this.getGroupEl(group)); + this.setActiveGroup(group); + }, + + getFrameHeight: function(){ + var h = this.el.getFrameWidth('tb'); + h += (this.tbar ? this.tbar.getHeight() : 0) + + (this.bbar ? this.bbar.getHeight() : 0); + + return h; + }, + + adjustBodyWidth: function(w){ + return w - this.tabWidth; + } +}); + +Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/* + * Note that this control will most likely remain as an example, and not as a core Ext form + * control. However, the API will be changing in a future release and so should not yet be + * treated as a final, stable API at this time. + */ + +/** + * @class Ext.ux.form.ItemSelector + * @extends Ext.form.Field + * A control that allows selection of between two Ext.ux.form.MultiSelect controls. + * + * @history + * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams) + * + * @constructor + * Create a new ItemSelector + * @param {Object} config Configuration options + * @xtype itemselector + */ +Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field, { + hideNavIcons:false, + imagePath:"", + iconUp:"up2.gif", + iconDown:"down2.gif", + iconLeft:"left2.gif", + iconRight:"right2.gif", + iconTop:"top2.gif", + iconBottom:"bottom2.gif", + drawUpIcon:true, + drawDownIcon:true, + drawLeftIcon:true, + drawRightIcon:true, + drawTopIcon:true, + drawBotIcon:true, + delimiter:',', + bodyStyle:null, + border:false, + defaultAutoCreate:{tag: "div"}, + /** + * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store) + */ + multiselects:null, + + initComponent: function(){ + Ext.ux.form.ItemSelector.superclass.initComponent.call(this); + this.addEvents({ + 'rowdblclick' : true, + 'change' : true + }); + }, + + onRender: function(ct, position){ + Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position); + + // Internal default configuration for both multiselects + var msConfig = [{ + legend: 'Available', + draggable: true, + droppable: true, + width: 100, + height: 100 + },{ + legend: 'Selected', + droppable: true, + draggable: true, + width: 100, + height: 100 + }]; + + this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0])); + this.fromMultiselect.on('dblclick', this.onRowDblClick, this); + + this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1])); + this.toMultiselect.on('dblclick', this.onRowDblClick, this); + + var p = new Ext.Panel({ + bodyStyle:this.bodyStyle, + border:this.border, + layout:"table", + layoutConfig:{columns:3} + }); + + p.add(this.fromMultiselect); + var icons = new Ext.Panel({header:false}); + p.add(icons); + p.add(this.toMultiselect); + p.render(this.el); + icons.el.down('.'+icons.bwrapCls).remove(); + + // ICON HELL!!! + if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/") + this.imagePath+="/"; + this.iconUp = this.imagePath + (this.iconUp || 'up2.gif'); + this.iconDown = this.imagePath + (this.iconDown || 'down2.gif'); + this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif'); + this.iconRight = this.imagePath + (this.iconRight || 'right2.gif'); + this.iconTop = this.imagePath + (this.iconTop || 'top2.gif'); + this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif'); + var el=icons.getEl(); + this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}}); + el.createChild({tag: 'br'}); + this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}}); + el.createChild({tag: 'br'}); + this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}}); + el.createChild({tag: 'br'}); + this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}}); + el.createChild({tag: 'br'}); + this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}}); + el.createChild({tag: 'br'}); + this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}}); + this.toTopIcon.on('click', this.toTop, this); + this.upIcon.on('click', this.up, this); + this.downIcon.on('click', this.down, this); + this.toBottomIcon.on('click', this.toBottom, this); + this.addIcon.on('click', this.fromTo, this); + this.removeIcon.on('click', this.toFrom, this); + if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; } + if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; } + if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; } + if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; } + if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; } + if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; } + + var tb = p.body.first(); + this.el.setWidth(p.body.first().getWidth()); + p.body.removeClass(); + + this.hiddenName = this.name; + var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name}; + this.hiddenField = this.el.createChild(hiddenTag); + }, + + doLayout: function(){ + if(this.rendered){ + this.fromMultiselect.fs.doLayout(); + this.toMultiselect.fs.doLayout(); + } + }, + + afterRender: function(){ + Ext.ux.form.ItemSelector.superclass.afterRender.call(this); + + this.toStore = this.toMultiselect.store; + this.toStore.on('add', this.valueChanged, this); + this.toStore.on('remove', this.valueChanged, this); + this.toStore.on('load', this.valueChanged, this); + this.valueChanged(this.toStore); + }, + + toTop : function() { + var selectionsArray = this.toMultiselect.view.getSelectedIndexes(); + var records = []; + if (selectionsArray.length > 0) { + selectionsArray.sort(); + for (var i=0; i-1; i--) { + record = records[i]; + this.toMultiselect.view.store.remove(record); + this.toMultiselect.view.store.insert(0, record); + selectionsArray.push(((records.length - 1) - i)); + } + } + this.toMultiselect.view.refresh(); + this.toMultiselect.view.select(selectionsArray); + }, + + toBottom : function() { + var selectionsArray = this.toMultiselect.view.getSelectedIndexes(); + var records = []; + if (selectionsArray.length > 0) { + selectionsArray.sort(); + for (var i=0; i 0) { + for (var i=0; i= 0) { + this.toMultiselect.view.store.remove(record); + this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record); + newSelectionsArray.push(selectionsArray[i] - 1); + } + } + this.toMultiselect.view.refresh(); + this.toMultiselect.view.select(newSelectionsArray); + } + }, + + down : function() { + var record = null; + var selectionsArray = this.toMultiselect.view.getSelectedIndexes(); + selectionsArray.sort(); + selectionsArray.reverse(); + var newSelectionsArray = []; + if (selectionsArray.length > 0) { + for (var i=0; i 0) { + for (var i=0; i 0) { + for (var i=0; iundefined). + * Acceptable values for this property are: + *
      + *
    • any {@link Ext.data.Store Store} subclass
    • + *
    • an Array : Arrays will be converted to a {@link Ext.data.ArrayStore} internally. + *
        + *
      • 1-dimensional array : (e.g., ['Foo','Bar'])
        + * A 1-dimensional array will automatically be expanded (each array item will be the combo + * {@link #valueField value} and {@link #displayField text})
      • + *
      • 2-dimensional array : (e.g., [['f','Foo'],['b','Bar']])
        + * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo + * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}. + *
    + */ + + // private + defaultAutoCreate : {tag: "div"}, + + // private + initComponent: function(){ + Ext.ux.form.MultiSelect.superclass.initComponent.call(this); + + if(Ext.isArray(this.store)){ + if (Ext.isArray(this.store[0])){ + this.store = new Ext.data.ArrayStore({ + fields: ['value','text'], + data: this.store + }); + this.valueField = 'value'; + }else{ + this.store = new Ext.data.ArrayStore({ + fields: ['text'], + data: this.store, + expandData: true + }); + this.valueField = 'text'; + } + this.displayField = 'text'; + } else { + this.store = Ext.StoreMgr.lookup(this.store); + } + + this.addEvents({ + 'dblclick' : true, + 'click' : true, + 'change' : true, + 'drop' : true + }); + }, + + // private + onRender: function(ct, position){ + Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position); + + var fs = this.fs = new Ext.form.FieldSet({ + renderTo: this.el, + title: this.legend, + height: this.height, + width: this.width, + style: "padding:0;", + tbar: this.tbar, + bodyStyle: 'overflow: auto;' + }); + + this.view = new Ext.ListView({ + multiSelect: true, + store: this.store, + columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }], + hideHeaders: true + }); + + fs.add(this.view); + + this.view.on('click', this.onViewClick, this); + this.view.on('beforeclick', this.onViewBeforeClick, this); + this.view.on('dblclick', this.onViewDblClick, this); + + this.hiddenName = this.name || Ext.id(); + var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName }; + this.hiddenField = this.el.createChild(hiddenTag); + this.hiddenField.dom.disabled = this.hiddenName != this.name; + fs.doLayout(); + }, + + // private + afterRender: function(){ + Ext.ux.form.MultiSelect.superclass.afterRender.call(this); + + if (this.ddReorder && !this.dragGroup && !this.dropGroup){ + this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id(); + } + + if (this.draggable || this.dragGroup){ + this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, { + ddGroup: this.dragGroup + }); + } + if (this.droppable || this.dropGroup){ + this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, { + ddGroup: this.dropGroup + }); + } + }, + + // private + onViewClick: function(vw, index, node, e) { + this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value); + this.hiddenField.dom.value = this.getValue(); + this.fireEvent('click', this, e); + this.validate(); + }, + + // private + onViewBeforeClick: function(vw, index, node, e) { + if (this.disabled) {return false;} + }, + + // private + onViewDblClick : function(vw, index, node, e) { + return this.fireEvent('dblclick', vw, index, node, e); + }, + + /** + * Returns an array of data values for the selected items in the list. The values will be separated + * by {@link #delimiter}. + * @return {Array} value An array of string data values + */ + getValue: function(valueField){ + var returnArray = []; + var selectionsArray = this.view.getSelectedIndexes(); + if (selectionsArray.length == 0) {return '';} + for (var i=0; i this.maxSelections) { + this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections)); + return false; + } + return true; + }, + + // inherit docs + disable: function(){ + this.disabled = true; + this.hiddenField.dom.disabled = true; + this.fs.disable(); + }, + + // inherit docs + enable: function(){ + this.disabled = false; + this.hiddenField.dom.disabled = false; + this.fs.enable(); + }, + + // inherit docs + destroy: function(){ + Ext.destroy(this.fs, this.dragZone, this.dropZone); + Ext.ux.form.MultiSelect.superclass.destroy.call(this); + } +}); + + +Ext.reg('multiselect', Ext.ux.form.MultiSelect); + +//backwards compat +Ext.ux.Multiselect = Ext.ux.form.MultiSelect; + + +Ext.ux.form.MultiSelect.DragZone = function(ms, config){ + this.ms = ms; + this.view = ms.view; + var ddGroup = config.ddGroup || 'MultiselectDD'; + var dd; + if (Ext.isArray(ddGroup)){ + dd = ddGroup.shift(); + } else { + dd = ddGroup; + ddGroup = null; + } + Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd }); + this.setDraggable(ddGroup); +}; + +Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, { + onInitDrag : function(x, y){ + var el = Ext.get(this.dragData.ddel.cloneNode(true)); + this.proxy.update(el.dom); + el.setWidth(el.child('em').getWidth()); + this.onStartDrag(x, y); + return true; + }, + + // private + collectSelection: function(data) { + data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY(); + var i = 0; + this.view.store.each(function(rec){ + if (this.view.isSelected(i)) { + var n = this.view.getNode(i); + var dragNode = n.cloneNode(true); + dragNode.id = Ext.id(); + data.ddel.appendChild(dragNode); + data.records.push(this.view.store.getAt(i)); + data.viewNodes.push(n); + } + i++; + }, this); + }, + + // override + onEndDrag: function(data, e) { + var d = Ext.get(this.dragData.ddel); + if (d && d.hasClass("multi-proxy")) { + d.remove(); + } + }, + + // override + getDragData: function(e){ + var target = this.view.findItemFromChild(e.getTarget()); + if(target) { + if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) { + this.view.select(target); + this.ms.setValue(this.ms.getValue()); + } + if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false; + var dragData = { + sourceView: this.view, + viewNodes: [], + records: [] + }; + if (this.view.getSelectionCount() == 1) { + var i = this.view.getSelectedIndexes()[0]; + var n = this.view.getNode(i); + dragData.viewNodes.push(dragData.ddel = n); + dragData.records.push(this.view.store.getAt(i)); + dragData.repairXY = Ext.fly(n).getXY(); + } else { + dragData.ddel = document.createElement('div'); + dragData.ddel.className = 'multi-proxy'; + this.collectSelection(dragData); + } + return dragData; + } + return false; + }, + + // override the default repairXY. + getRepairXY : function(e){ + return this.dragData.repairXY; + }, + + // private + setDraggable: function(ddGroup){ + if (!ddGroup) return; + if (Ext.isArray(ddGroup)) { + Ext.each(ddGroup, this.setDraggable, this); + return; + } + this.addToGroup(ddGroup); + } +}); + +Ext.ux.form.MultiSelect.DropZone = function(ms, config){ + this.ms = ms; + this.view = ms.view; + var ddGroup = config.ddGroup || 'MultiselectDD'; + var dd; + if (Ext.isArray(ddGroup)){ + dd = ddGroup.shift(); + } else { + dd = ddGroup; + ddGroup = null; + } + Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd }); + this.setDroppable(ddGroup); +}; + +Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, { + /** + * Part of the Ext.dd.DropZone interface. If no target node is found, the + * whole Element becomes the target, and this causes the drop gesture to append. + */ + getTargetFromEvent : function(e) { + var target = e.getTarget(); + return target; + }, + + // private + getDropPoint : function(e, n, dd){ + if (n == this.ms.fs.body.dom) { return "below"; } + var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight; + var c = t + (b - t) / 2; + var y = Ext.lib.Event.getPageY(e); + if(y <= c) { + return "above"; + }else{ + return "below"; + } + }, + + // private + isValidDropPoint: function(pt, n, data) { + if (!data.viewNodes || (data.viewNodes.length != 1)) { + return true; + } + var d = data.viewNodes[0]; + if (d == n) { + return false; + } + if ((pt == "below") && (n.nextSibling == d)) { + return false; + } + if ((pt == "above") && (n.previousSibling == d)) { + return false; + } + return true; + }, + + // override + onNodeEnter : function(n, dd, e, data){ + return false; + }, + + // override + onNodeOver : function(n, dd, e, data){ + var dragElClass = this.dropNotAllowed; + var pt = this.getDropPoint(e, n, dd); + if (this.isValidDropPoint(pt, n, data)) { + if (this.ms.appendOnly) { + return "x-tree-drop-ok-below"; + } + + // set the insert point style on the target node + if (pt) { + var targetElClass; + if (pt == "above"){ + dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above"; + targetElClass = "x-view-drag-insert-above"; + } else { + dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below"; + targetElClass = "x-view-drag-insert-below"; + } + if (this.lastInsertClass != targetElClass){ + Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass); + this.lastInsertClass = targetElClass; + } + } + } + return dragElClass; + }, + + // private + onNodeOut : function(n, dd, e, data){ + this.removeDropIndicators(n); + }, + + // private + onNodeDrop : function(n, dd, e, data){ + if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) { + return false; + } + var pt = this.getDropPoint(e, n, dd); + if (n != this.ms.fs.body.dom) + n = this.view.findItemFromChild(n); + var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n); + if (pt == "below") { + insertAt++; + } + + var dir = false; + + // Validate if dragging within the same MultiSelect + if (data.sourceView == this.view) { + // If the first element to be inserted below is the target node, remove it + if (pt == "below") { + if (data.viewNodes[0] == n) { + data.viewNodes.shift(); + } + } else { // If the last element to be inserted above is the target node, remove it + if (data.viewNodes[data.viewNodes.length - 1] == n) { + data.viewNodes.pop(); + } + } + + // Nothing to drop... + if (!data.viewNodes.length) { + return false; + } + + // If we are moving DOWN, then because a store.remove() takes place first, + // the insertAt must be decremented. + if (insertAt > this.view.store.indexOf(data.records[0])) { + dir = 'down'; + insertAt--; + } + } + + for (var i = 0; i < data.records.length; i++) { + var r = data.records[i]; + if (data.sourceView) { + data.sourceView.store.remove(r); + } + this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r); + var si = this.view.store.sortInfo; + if(si){ + this.view.store.sort(si.field, si.direction); + } + } + return true; + }, + + // private + removeDropIndicators : function(n){ + if(n){ + Ext.fly(n).removeClass([ + "x-view-drag-insert-above", + "x-view-drag-insert-left", + "x-view-drag-insert-right", + "x-view-drag-insert-below"]); + this.lastInsertClass = "_noclass"; + } + }, + + // private + setDroppable: function(ddGroup){ + if (!ddGroup) return; + if (Ext.isArray(ddGroup)) { + Ext.each(ddGroup, this.setDroppable, this); + return; + } + this.addToGroup(ddGroup); + } +}); + +/* Fix for Opera, which does not seem to include the map function on Array's */ +if (!Array.prototype.map) { + Array.prototype.map = function(fun){ + var len = this.length; + if (typeof fun != 'function') { + throw new TypeError(); + } + var res = new Array(len); + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in this) { + res[i] = fun.call(thisp, this[i], i, this); + } + } + return res; + }; +} + +Ext.ns('Ext.ux.data'); + +/** + * @class Ext.ux.data.PagingMemoryProxy + * @extends Ext.data.MemoryProxy + *

    Paging Memory Proxy, allows to use paging grid with in memory dataset

    + */ +Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, { + constructor : function(data){ + Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this); + this.data = data; + }, + doRequest : function(action, rs, params, reader, callback, scope, options){ + params = params || + {}; + var result; + try { + result = reader.readRecords(this.data); + } + catch (e) { + this.fireEvent('loadexception', this, options, null, e); + callback.call(scope, null, options, false); + return; + } + + // filtering + if (params.filter !== undefined) { + result.records = result.records.filter(function(el){ + if (typeof(el) == 'object') { + var att = params.filterCol || 0; + return String(el.data[att]).match(params.filter) ? true : false; + } + else { + return String(el).match(params.filter) ? true : false; + } + }); + result.totalRecords = result.records.length; + } + + // sorting + if (params.sort !== undefined) { + // use integer as params.sort to specify column, since arrays are not named + // params.sort=0; would also match a array without columns + var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1; + var fn = function(r1, r2){ + return r1 < r2; + }; + result.records.sort(function(a, b){ + var v = 0; + if (typeof(a) == 'object') { + v = fn(a.data[params.sort], b.data[params.sort]) * dir; + } + else { + v = fn(a, b) * dir; + } + if (v == 0) { + v = (a.index < b.index ? -1 : 1); + } + return v; + }); + } + // paging (use undefined cause start can also be 0 (thus false)) + if (params.start !== undefined && params.limit !== undefined) { + result.records = result.records.slice(params.start, params.start + params.limit); + } + callback.call(scope, result, options, true); + } +}); + +//backwards compat. +Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy; +Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, { + minHeight: 0, + maxHeight:10000000, + + constructor: function(config){ + Ext.apply(this, config); + this.events = {}; + Ext.ux.PanelResizer.superclass.constructor.call(this, config); + }, + + init : function(p){ + this.panel = p; + + if(this.panel.elements.indexOf('footer')==-1){ + p.elements += ',footer'; + } + p.on('render', this.onRender, this); + }, + + onRender : function(p){ + this.handle = p.footer.createChild({cls:'x-panel-resize'}); + + this.tracker = new Ext.dd.DragTracker({ + onStart: this.onDragStart.createDelegate(this), + onDrag: this.onDrag.createDelegate(this), + onEnd: this.onDragEnd.createDelegate(this), + tolerance: 3, + autoStart: 300 + }); + this.tracker.initEl(this.handle); + p.on('beforedestroy', this.tracker.destroy, this.tracker); + }, + + // private + onDragStart: function(e){ + this.dragging = true; + this.startHeight = this.panel.el.getHeight(); + this.fireEvent('dragstart', this, e); + }, + + // private + onDrag: function(e){ + this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight)); + this.fireEvent('drag', this, e); + }, + + // private + onDragEnd: function(e){ + this.dragging = false; + this.fireEvent('dragend', this, e); + } +}); +Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, { + layout : 'column', + autoScroll : true, + cls : 'x-portal', + defaultType : 'portalcolumn', + + initComponent : function(){ + Ext.ux.Portal.superclass.initComponent.call(this); + this.addEvents({ + validatedrop:true, + beforedragover:true, + dragover:true, + beforedrop:true, + drop:true + }); + }, + + initEvents : function(){ + Ext.ux.Portal.superclass.initEvents.call(this); + this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig); + }, + + beforeDestroy : function() { + if(this.dd){ + this.dd.unreg(); + } + Ext.ux.Portal.superclass.beforeDestroy.call(this); + } +}); + +Ext.reg('portal', Ext.ux.Portal); + + +Ext.ux.Portal.DropZone = function(portal, cfg){ + this.portal = portal; + Ext.dd.ScrollManager.register(portal.body); + Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg); + portal.body.ddScrollConfig = this.ddScrollConfig; +}; + +Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, { + ddScrollConfig : { + vthresh: 50, + hthresh: -1, + animate: true, + increment: 200 + }, + + createEvent : function(dd, e, data, col, c, pos){ + return { + portal: this.portal, + panel: data.panel, + columnIndex: col, + column: c, + position: pos, + data: data, + source: dd, + rawEvent: e, + status: this.dropAllowed + }; + }, + + notifyOver : function(dd, e, data){ + var xy = e.getXY(), portal = this.portal, px = dd.proxy; + + // case column widths + if(!this.grid){ + this.grid = this.getGrid(); + } + + // handle case scroll where scrollbars appear during drag + var cw = portal.body.dom.clientWidth; + if(!this.lastCW){ + this.lastCW = cw; + }else if(this.lastCW != cw){ + this.lastCW = cw; + portal.doLayout(); + this.grid = this.getGrid(); + } + + // determine column + var col = 0, xs = this.grid.columnX, cmatch = false; + for(var len = xs.length; col < len; col++){ + if(xy[0] < (xs[col].x + xs[col].w)){ + cmatch = true; + break; + } + } + // no match, fix last index + if(!cmatch){ + col--; + } + + // find insert position + var p, match = false, pos = 0, + c = portal.items.itemAt(col), + items = c.items.items, overSelf = false; + + for(var len = items.length; pos < len; pos++){ + p = items[pos]; + var h = p.el.getHeight(); + if(h === 0){ + overSelf = true; + } + else if((p.el.getY()+(h/2)) > xy[1]){ + match = true; + break; + } + } + + pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0); + var overEvent = this.createEvent(dd, e, data, col, c, pos); + + if(portal.fireEvent('validatedrop', overEvent) !== false && + portal.fireEvent('beforedragover', overEvent) !== false){ + + // make sure proxy width is fluid + px.getProxy().setWidth('auto'); + + if(p){ + px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null); + }else{ + px.moveProxy(c.el.dom, null); + } + + this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false}; + this.scrollPos = portal.body.getScroll(); + + portal.fireEvent('dragover', overEvent); + + return overEvent.status; + }else{ + return overEvent.status; + } + + }, + + notifyOut : function(){ + delete this.grid; + }, + + notifyDrop : function(dd, e, data){ + delete this.grid; + if(!this.lastPos){ + return; + } + var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p; + + var dropEvent = this.createEvent(dd, e, data, col, c, + pos !== false ? pos : c.items.getCount()); + + if(this.portal.fireEvent('validatedrop', dropEvent) !== false && + this.portal.fireEvent('beforedrop', dropEvent) !== false){ + + dd.proxy.getProxy().remove(); + dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom); + + if(pos !== false){ + if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){ + pos++; + } + c.insert(pos, dd.panel); + }else{ + c.add(dd.panel); + } + + c.doLayout(); + + this.portal.fireEvent('drop', dropEvent); + + // scroll position is lost on drop, fix it + var st = this.scrollPos.top; + if(st){ + var d = this.portal.body.dom; + setTimeout(function(){ + d.scrollTop = st; + }, 10); + } + + } + delete this.lastPos; + }, + + // internal cache of body and column coords + getGrid : function(){ + var box = this.portal.bwrap.getBox(); + box.columnX = []; + this.portal.items.each(function(c){ + box.columnX.push({x: c.el.getX(), w: c.el.getWidth()}); + }); + return box; + }, + + // unregister the dropzone from ScrollManager + unreg: function() { + //Ext.dd.ScrollManager.unregister(this.portal.body); + Ext.ux.Portal.DropZone.superclass.unreg.call(this); + } +}); +Ext.ux.PortalColumn = Ext.extend(Ext.Container, { + layout : 'anchor', + //autoEl : 'div',//already defined by Ext.Component + defaultType : 'portlet', + cls : 'x-portal-column' +}); + +Ext.reg('portalcolumn', Ext.ux.PortalColumn); +Ext.ux.Portlet = Ext.extend(Ext.Panel, { + anchor : '100%', + frame : true, + collapsible : true, + draggable : true, + cls : 'x-portlet' +}); + +Ext.reg('portlet', Ext.ux.Portlet); +/** +* @class Ext.ux.ProgressBarPager +* @extends Object +* Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text +* +* @ptype progressbarpager +* @constructor +* Create a new ItemSelector +* @param {Object} config Configuration options +* @xtype itemselector +*/ +Ext.ux.ProgressBarPager = Ext.extend(Object, { + /** + * @cfg {Integer} progBarWidth + *

    The default progress bar width. Default is 225.

    + */ + progBarWidth : 225, + /** + * @cfg {String} defaultText + *

    The text to display while the store is loading. Default is 'Loading...'

    + */ + defaultText : 'Loading...', + /** + * @cfg {Object} defaultAnimCfg + *

    A {@link Ext.Fx Ext.Fx} configuration object. Default is { duration : 1, easing : 'bounceOut' }.

    + */ + defaultAnimCfg : { + duration : 1, + easing : 'bounceOut' + }, + constructor : function(config) { + if (config) { + Ext.apply(this, config); + } + }, + //public + init : function (parent) { + + if(parent.displayInfo){ + this.parent = parent; + var ind = parent.items.indexOf(parent.displayItem); + parent.remove(parent.displayItem, true); + this.progressBar = new Ext.ProgressBar({ + text : this.defaultText, + width : this.progBarWidth, + animate : this.defaultAnimCfg + }); + + parent.displayItem = this.progressBar; + + parent.add(parent.displayItem); + parent.doLayout(); + Ext.apply(parent, this.parentOverrides); + + this.progressBar.on('render', function(pb) { + pb.el.applyStyles('cursor:pointer'); + + pb.el.on('click', this.handleProgressBarClick, this); + }, this); + + + // Remove the click handler from the + this.progressBar.on({ + scope : this, + beforeDestroy : function() { + this.progressBar.el.un('click', this.handleProgressBarClick, this); + } + }); + + } + + }, + // private + // This method handles the click for the progress bar + handleProgressBarClick : function(e){ + var parent = this.parent; + var displayItem = parent.displayItem; + + var box = this.progressBar.getBox(); + var xy = e.getXY(); + var position = xy[0]-box.x; + var pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize); + + var newpage = Math.ceil(position/(displayItem.width/pages)); + parent.changePage(newpage); + }, + + // private, overriddes + parentOverrides : { + // private + // This method updates the information via the progress bar. + updateInfo : function(){ + if(this.displayItem){ + var count = this.store.getCount(); + var pgData = this.getPageData(); + var pageNum = this.readPage(pgData); + + var msg = count == 0 ? + this.emptyMsg : + String.format( + this.displayMsg, + this.cursor+1, this.cursor+count, this.store.getTotalCount() + ); + + pageNum = pgData.activePage; ; + + var pct = pageNum / pgData.pages; + + this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig); + } + } + } +}); +Ext.preg('progressbarpager', Ext.ux.ProgressBarPager); + +Ext.ns('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.RowEditor + * @extends Ext.Panel + * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid. + * A validation mode may be enabled which uses AnchorTips to notify the user of all + * validation errors at once. + * + * @ptype roweditor + */ +Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, { + floating: true, + shadow: false, + layout: 'hbox', + cls: 'x-small-editor', + buttonAlign: 'center', + baseCls: 'x-row-editor', + elements: 'header,footer,body', + frameWidth: 5, + buttonPad: 3, + clicksToEdit: 'auto', + monitorValid: true, + focusDelay: 250, + errorSummary: true, + + defaults: { + normalWidth: true + }, + + initComponent: function(){ + Ext.ux.grid.RowEditor.superclass.initComponent.call(this); + this.addEvents( + /** + * @event beforeedit + * Fired before the row editor is activated. + * If the listener returns false the editor will not be activated. + * @param {Ext.ux.grid.RowEditor} roweditor This object + * @param {Number} rowIndex The rowIndex of the row just edited + */ + 'beforeedit', + /** + * @event validateedit + * Fired after a row is edited and passes validation. + * If the listener returns false changes to the record will not be set. + * @param {Ext.ux.grid.RowEditor} roweditor This object + * @param {Object} changes Object with changes made to the record. + * @param {Ext.data.Record} r The Record that was edited. + * @param {Number} rowIndex The rowIndex of the row just edited + */ + 'validateedit', + /** + * @event afteredit + * Fired after a row is edited and passes validation. This event is fired + * after the store's update event is fired with this edit. + * @param {Ext.ux.grid.RowEditor} roweditor This object + * @param {Object} changes Object with changes made to the record. + * @param {Ext.data.Record} r The Record that was edited. + * @param {Number} rowIndex The rowIndex of the row just edited + */ + 'afteredit' + ); + }, + + init: function(grid){ + this.grid = grid; + this.ownerCt = grid; + if(this.clicksToEdit === 2){ + grid.on('rowdblclick', this.onRowDblClick, this); + }else{ + grid.on('rowclick', this.onRowClick, this); + if(Ext.isIE){ + grid.on('rowdblclick', this.onRowDblClick, this); + } + } + + // stopEditing without saving when a record is removed from Store. + grid.getStore().on('remove', function() { + this.stopEditing(false); + },this); + + grid.on({ + scope: this, + keydown: this.onGridKey, + columnresize: this.verifyLayout, + columnmove: this.refreshFields, + reconfigure: this.refreshFields, + destroy : this.destroy, + bodyscroll: { + buffer: 250, + fn: this.positionButtons + } + }); + grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1}); + grid.getView().on('refresh', this.stopEditing.createDelegate(this, [])); + }, + + refreshFields: function(){ + this.initFields(); + this.verifyLayout(); + }, + + isDirty: function(){ + var dirty; + this.items.each(function(f){ + if(String(this.values[f.id]) !== String(f.getValue())){ + dirty = true; + return false; + } + }, this); + return dirty; + }, + + startEditing: function(rowIndex, doFocus){ + if(this.editing && this.isDirty()){ + this.showTooltip('You need to commit or cancel your changes'); + return; + } + this.editing = true; + if(typeof rowIndex == 'object'){ + rowIndex = this.grid.getStore().indexOf(rowIndex); + } + if(this.fireEvent('beforeedit', this, rowIndex) !== false){ + var g = this.grid, view = g.getView(); + var row = view.getRow(rowIndex); + var record = g.store.getAt(rowIndex); + this.record = record; + this.rowIndex = rowIndex; + this.values = {}; + if(!this.rendered){ + this.render(view.getEditorParent()); + } + var w = Ext.fly(row).getWidth(); + this.setSize(w); + if(!this.initialized){ + this.initFields(); + } + var cm = g.getColumnModel(), fields = this.items.items, f, val; + for(var i = 0, len = cm.getColumnCount(); i < len; i++){ + val = this.preEditValue(record, cm.getDataIndex(i)); + f = fields[i]; + f.setValue(val); + this.values[f.id] = val || ''; + } + this.verifyLayout(true); + if(!this.isVisible()){ + this.setPagePosition(Ext.fly(row).getXY()); + } else{ + this.el.setXY(Ext.fly(row).getXY(), {duration:0.15}); + } + if(!this.isVisible()){ + this.show().doLayout(); + } + if(doFocus !== false){ + this.doFocus.defer(this.focusDelay, this); + } + } + }, + + stopEditing : function(saveChanges){ + this.editing = false; + if(!this.isVisible()){ + return; + } + if(saveChanges === false || !this.isValid()){ + this.hide(); + return; + } + var changes = {}, r = this.record, hasChange = false; + var cm = this.grid.colModel, fields = this.items.items; + for(var i = 0, len = cm.getColumnCount(); i < len; i++){ + if(!cm.isHidden(i)){ + var dindex = cm.getDataIndex(i); + if(!Ext.isEmpty(dindex)){ + var oldValue = r.data[dindex]; + var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex); + if(String(oldValue) !== String(value)){ + changes[dindex] = value; + hasChange = true; + } + } + } + } + if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){ + r.beginEdit(); + for(var k in changes){ + if(changes.hasOwnProperty(k)){ + r.set(k, changes[k]); + } + } + r.endEdit(); + this.fireEvent('afteredit', this, changes, r, this.rowIndex); + } + this.hide(); + }, + + verifyLayout: function(force){ + if(this.el && (this.isVisible() || force === true)){ + var row = this.grid.getView().getRow(this.rowIndex); + this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + (Ext.isBorderBox ? 9 : 0) : undefined); + var cm = this.grid.colModel, fields = this.items.items; + for(var i = 0, len = cm.getColumnCount(); i < len; i++){ + if(!cm.isHidden(i)){ + var adjust = 0; + if(i === 0){ + adjust += 0; // outer padding + } + if(i === (len - 1)){ + adjust += 3; // outer padding + } else{ + adjust += 1; + } + fields[i].show(); + fields[i].setWidth(cm.getColumnWidth(i) - adjust); + } else{ + fields[i].hide(); + } + } + this.doLayout(); + this.positionButtons(); + } + }, + + slideHide : function(){ + this.hide(); + }, + + initFields: function(){ + var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins; + this.removeAll(false); + for(var i = 0, len = cm.getColumnCount(); i < len; i++){ + var c = cm.getColumnAt(i); + var ed = c.getEditor(); + if(!ed){ + ed = c.displayEditor || new Ext.form.DisplayField(); + } + if(i == 0){ + ed.margins = pm('0 1 2 1'); + } else if(i == len - 1){ + ed.margins = pm('0 0 2 1'); + } else{ + ed.margins = pm('0 1 2'); + } + ed.setWidth(cm.getColumnWidth(i)); + ed.column = c; + if(ed.ownerCt !== this){ + ed.on('focus', this.ensureVisible, this); + ed.on('specialkey', this.onKey, this); + } + this.insert(i, ed); + } + this.initialized = true; + }, + + onKey: function(f, e){ + if(e.getKey() === e.ENTER){ + this.stopEditing(true); + e.stopPropagation(); + } + }, + + onGridKey: function(e){ + if(e.getKey() === e.ENTER && !this.isVisible()){ + var r = this.grid.getSelectionModel().getSelected(); + if(r){ + var index = this.grid.store.indexOf(r); + this.startEditing(index); + e.stopPropagation(); + } + } + }, + + ensureVisible: function(editor){ + if(this.isVisible()){ + this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true); + } + }, + + onRowClick: function(g, rowIndex, e){ + if(this.clicksToEdit == 'auto'){ + var li = this.lastClickIndex; + this.lastClickIndex = rowIndex; + if(li != rowIndex && !this.isVisible()){ + return; + } + } + this.startEditing(rowIndex, false); + this.doFocus.defer(this.focusDelay, this, [e.getPoint()]); + }, + + onRowDblClick: function(g, rowIndex, e){ + this.startEditing(rowIndex, false); + this.doFocus.defer(this.focusDelay, this, [e.getPoint()]); + }, + + onRender: function(){ + Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments); + this.el.swallowEvent(['keydown', 'keyup', 'keypress']); + this.btns = new Ext.Panel({ + baseCls: 'x-plain', + cls: 'x-btns', + elements:'body', + layout: 'table', + width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE + items: [{ + ref: 'saveBtn', + itemId: 'saveBtn', + xtype: 'button', + text: this.saveText || 'Save', + width: this.minButtonWidth, + handler: this.stopEditing.createDelegate(this, [true]) + }, { + xtype: 'button', + text: this.cancelText || 'Cancel', + width: this.minButtonWidth, + handler: this.stopEditing.createDelegate(this, [false]) + }] + }); + this.btns.render(this.bwrap); + }, + + afterRender: function(){ + Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments); + this.positionButtons(); + if(this.monitorValid){ + this.startMonitoring(); + } + }, + + onShow: function(){ + if(this.monitorValid){ + this.startMonitoring(); + } + Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments); + }, + + onHide: function(){ + Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments); + this.stopMonitoring(); + this.grid.getView().focusRow(this.rowIndex); + }, + + positionButtons: function(){ + if(this.btns){ + var h = this.el.dom.clientHeight; + var view = this.grid.getView(); + var scroll = view.scroller.dom.scrollLeft; + var width = view.mainBody.getWidth(); + var bw = this.btns.getWidth(); + this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2}); + } + }, + + // private + preEditValue : function(r, field){ + var value = r.data[field]; + return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value; + }, + + // private + postEditValue : function(value, originalValue, r, field){ + return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value; + }, + + doFocus: function(pt){ + if(this.isVisible()){ + var index = 0; + if(pt){ + index = this.getTargetColumnIndex(pt); + } + var cm = this.grid.getColumnModel(); + for(var i = index||0, len = cm.getColumnCount(); i < len; i++){ + var c = cm.getColumnAt(i); + if(!c.hidden && c.getEditor()){ + c.getEditor().focus(); + break; + } + } + } + }, + + getTargetColumnIndex: function(pt){ + var grid = this.grid, v = grid.view; + var x = pt.left; + var cms = grid.colModel.config; + var i = 0, match = false; + for(var len = cms.length, c; c = cms[i]; i++){ + if(!c.hidden){ + if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){ + match = i; + break; + } + } + } + return match; + }, + + startMonitoring : function(){ + if(!this.bound && this.monitorValid){ + this.bound = true; + Ext.TaskMgr.start({ + run : this.bindHandler, + interval : this.monitorPoll || 200, + scope: this + }); + } + }, + + stopMonitoring : function(){ + this.bound = false; + if(this.tooltip){ + this.tooltip.hide(); + } + }, + + isValid: function(){ + var valid = true; + this.items.each(function(f){ + if(!f.isValid(true)){ + valid = false; + return false; + } + }); + return valid; + }, + + // private + bindHandler : function(){ + if(!this.bound){ + return false; // stops binding + } + var valid = this.isValid(); + if(!valid && this.errorSummary){ + this.showTooltip(this.getErrorText().join('')); + } + this.btns.saveBtn.setDisabled(!valid); + this.fireEvent('validation', this, valid); + }, + + showTooltip: function(msg){ + var t = this.tooltip; + if(!t){ + t = this.tooltip = new Ext.ToolTip({ + maxWidth: 600, + cls: 'errorTip', + width: 300, + title: 'Errors', + autoHide: false, + anchor: 'left', + anchorToTarget: true, + mouseOffset: [40,0] + }); + } + t.initTarget(this.items.last().getEl()); + if(!t.rendered){ + t.show(); + t.hide(); + } + t.body.update(msg); + t.doAutoWidth(); + t.show(); + }, + + getErrorText: function(){ + var data = ['
      ']; + this.items.each(function(f){ + if(!f.isValid(true)){ + data.push('
    • ', f.activeError, '
    • '); + } + }); + data.push('
    '); + return data; + } +}); +Ext.preg('roweditor', Ext.ux.grid.RowEditor); + +Ext.override(Ext.form.Field, { + markInvalid : function(msg){ + if(!this.rendered || this.preventMark){ // not rendered + return; + } + msg = msg || this.invalidText; + + var mt = this.getMessageHandler(); + if(mt){ + mt.mark(this, msg); + }else if(this.msgTarget){ + this.el.addClass(this.invalidClass); + var t = Ext.getDom(this.msgTarget); + if(t){ + t.innerHTML = msg; + t.style.display = this.msgDisplay; + } + } + this.activeError = msg; + this.fireEvent('invalid', this, msg); + } +}); + +Ext.override(Ext.ToolTip, { + doAutoWidth : function(){ + var bw = this.body.getTextWidth(); + if(this.title){ + bw = Math.max(bw, this.header.child('span').getTextWidth(this.title)); + } + bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20; + this.setWidth(bw.constrain(this.minWidth, this.maxWidth)); + + // IE7 repaint bug on initial show + if(Ext.isIE7 && !this.repainted){ + this.el.repaint(); + this.repainted = true; + } + } +}); +Ext.ns('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.RowExpander + * @extends Ext.util.Observable + * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables + * a second row body which expands/contracts. The expand/contract behavior is configurable to react + * on clicking of the column, double click of the row, and/or hitting enter while a row is selected. + * + * @ptype rowexpander + */ +Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { + /** + * @cfg {Boolean} expandOnEnter + * true to toggle selected row(s) between expanded/collapsed when the enter + * key is pressed (defaults to true). + */ + expandOnEnter : true, + /** + * @cfg {Boolean} expandOnDblClick + * true to toggle a row between expanded/collapsed when double clicked + * (defaults to true). + */ + expandOnDblClick : true, + + header : '', + width : 20, + sortable : false, + fixed : true, + menuDisabled : true, + dataIndex : '', + id : 'expander', + lazyRender : true, + enableCaching : true, + + constructor: function(config){ + Ext.apply(this, config); + + this.addEvents({ + /** + * @event beforeexpand + * Fires before the row expands. Have the listener return false to prevent the row from expanding. + * @param {Object} this RowExpander object. + * @param {Object} Ext.data.Record Record for the selected row. + * @param {Object} body body element for the secondary row. + * @param {Number} rowIndex The current row index. + */ + beforeexpand: true, + /** + * @event expand + * Fires after the row expands. + * @param {Object} this RowExpander object. + * @param {Object} Ext.data.Record Record for the selected row. + * @param {Object} body body element for the secondary row. + * @param {Number} rowIndex The current row index. + */ + expand: true, + /** + * @event beforecollapse + * Fires before the row collapses. Have the listener return false to prevent the row from collapsing. + * @param {Object} this RowExpander object. + * @param {Object} Ext.data.Record Record for the selected row. + * @param {Object} body body element for the secondary row. + * @param {Number} rowIndex The current row index. + */ + beforecollapse: true, + /** + * @event collapse + * Fires after the row collapses. + * @param {Object} this RowExpander object. + * @param {Object} Ext.data.Record Record for the selected row. + * @param {Object} body body element for the secondary row. + * @param {Number} rowIndex The current row index. + */ + collapse: true + }); + + Ext.ux.grid.RowExpander.superclass.constructor.call(this); + + if(this.tpl){ + if(typeof this.tpl == 'string'){ + this.tpl = new Ext.Template(this.tpl); + } + this.tpl.compile(); + } + + this.state = {}; + this.bodyContent = {}; + }, + + getRowClass : function(record, rowIndex, p, ds){ + p.cols = p.cols-1; + var content = this.bodyContent[record.id]; + if(!content && !this.lazyRender){ + content = this.getBodyContent(record, rowIndex); + } + if(content){ + p.body = content; + } + return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed'; + }, + + init : function(grid){ + this.grid = grid; + + var view = grid.getView(); + view.getRowClass = this.getRowClass.createDelegate(this); + + view.enableRowBody = true; + + + grid.on('render', this.onRender, this); + grid.on('destroy', this.onDestroy, this); + }, + + // @private + onRender: function() { + var grid = this.grid; + var mainBody = grid.getView().mainBody; + mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'}); + if (this.expandOnEnter) { + this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), { + 'enter' : this.onEnter, + scope: this + }); + } + if (this.expandOnDblClick) { + grid.on('rowdblclick', this.onRowDblClick, this); + } + }, + + // @private + onDestroy: function() { + this.keyNav.disable(); + delete this.keyNav; + var mainBody = this.grid.getView().mainBody; + mainBody.un('mousedown', this.onMouseDown, this); + }, + // @private + onRowDblClick: function(grid, rowIdx, e) { + this.toggleRow(rowIdx); + }, + + onEnter: function(e) { + var g = this.grid; + var sm = g.getSelectionModel(); + var sels = sm.getSelections(); + for (var i = 0, len = sels.length; i < len; i++) { + var rowIdx = g.getStore().indexOf(sels[i]); + this.toggleRow(rowIdx); + } + }, + + getBodyContent : function(record, index){ + if(!this.enableCaching){ + return this.tpl.apply(record.data); + } + var content = this.bodyContent[record.id]; + if(!content){ + content = this.tpl.apply(record.data); + this.bodyContent[record.id] = content; + } + return content; + }, + + onMouseDown : function(e, t){ + e.stopEvent(); + var row = e.getTarget('.x-grid3-row'); + this.toggleRow(row); + }, + + renderer : function(v, p, record){ + p.cellAttr = 'rowspan="2"'; + return '
     
    '; + }, + + beforeExpand : function(record, body, rowIndex){ + if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){ + if(this.tpl && this.lazyRender){ + body.innerHTML = this.getBodyContent(record, rowIndex); + } + return true; + }else{ + return false; + } + }, + + toggleRow : function(row){ + if(typeof row == 'number'){ + row = this.grid.view.getRow(row); + } + this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row); + }, + + expandRow : function(row){ + if(typeof row == 'number'){ + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); + if(this.beforeExpand(record, body, row.rowIndex)){ + this.state[record.id] = true; + Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded'); + this.fireEvent('expand', this, record, body, row.rowIndex); + } + }, + + collapseRow : function(row){ + if(typeof row == 'number'){ + row = this.grid.view.getRow(row); + } + var record = this.grid.store.getAt(row.rowIndex); + var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); + if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){ + this.state[record.id] = false; + Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed'); + this.fireEvent('collapse', this, record, body, row.rowIndex); + } + } +}); + +Ext.preg('rowexpander', Ext.ux.grid.RowExpander); + +//backwards compat +Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not +// exist by default in Ext, so we have to add the namespace first: +Ext.ns('Ext.ux.layout'); + +/** + * @class Ext.ux.layout.RowLayout + * @extends Ext.layout.ContainerLayout + *

    This is the layout style of choice for creating structural layouts in a multi-row format where the height of + * each row can be specified as a percentage or fixed height. Row widths can also be fixed, percentage or auto. + * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config, + * and should generally not need to be created directly via the new keyword.

    + *

    RowLayout does not have any direct config options (other than inherited ones), but it does support a + * specific config property of rowHeight that can be included in the config of any panel added to it. The + * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel. + * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).

    + *

    The height property is always evaluated as pixels, and must be a number greater than or equal to 1. + * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and + * less than 1 (e.g., .25).

    + *

    The basic rules for specifying row heights are pretty simple. The logic makes two passes through the + * set of contained panels. During the first layout pass, all panels that either have a fixed height or none + * specified (auto) are skipped, but their heights are subtracted from the overall container height. During the second + * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on + * the total remaining container height. In other words, percentage height panels are designed to fill the space + * left over by all the fixed-height and/or auto-height panels. Because of this, while you can specify any number of rows + * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your + * layout may not render as expected. Example usage:

    + *
    
    +// All rows are percentages -- they must add up to 1
    +var p = new Ext.Panel({
    +    title: 'Row Layout - Percentage Only',
    +    layout:'ux.row',
    +    items: [{
    +        title: 'Row 1',
    +        rowHeight: .25
    +    },{
    +        title: 'Row 2',
    +        rowHeight: .6
    +    },{
    +        title: 'Row 3',
    +        rowHeight: .15
    +    }]
    +});
    +
    +// Mix of height and rowHeight -- all rowHeight values must add
    +// up to 1. The first row will take up exactly 120px, and the last two
    +// rows will fill the remaining container height.
    +var p = new Ext.Panel({
    +    title: 'Row Layout - Mixed',
    +    layout:'ux.row',
    +    items: [{
    +        title: 'Row 1',
    +        height: 120,
    +        // standard panel widths are still supported too:
    +        width: '50%' // or 200
    +    },{
    +        title: 'Row 2',
    +        rowHeight: .8,
    +        width: 300
    +    },{
    +        title: 'Row 3',
    +        rowHeight: .2
    +    }]
    +});
    +
    + */ +Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, { + // private + monitorResize:true, + + // private + isValidParent : function(c, target){ + return c.getEl().dom.parentNode == this.innerCt.dom; + }, + + // private + onLayout : function(ct, target){ + var rs = ct.items.items, len = rs.length, r, i; + + if(!this.innerCt){ + target.addClass('ux-row-layout-ct'); + this.innerCt = target.createChild({cls:'x-row-inner'}); + } + this.renderAll(ct, this.innerCt); + + var size = target.getViewSize(); + + if(size.width < 1 && size.height < 1){ // display none? + return; + } + + var h = size.height - target.getPadding('tb'), + ph = h; + + this.innerCt.setSize({height:h}); + + // some rows can be percentages while others are fixed + // so we need to make 2 passes + + for(i = 0; i < len; i++){ + r = rs[i]; + if(!r.rowHeight){ + ph -= (r.getSize().height + r.getEl().getMargins('tb')); + } + } + + ph = ph < 0 ? 0 : ph; + + for(i = 0; i < len; i++){ + r = rs[i]; + if(r.rowHeight){ + r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')}); + } + } + } + + /** + * @property activeItem + * @hide + */ +}); + +Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout; +Ext.ns('Ext.ux.form'); + +Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, { + initComponent : function(){ + Ext.ux.form.SearchField.superclass.initComponent.call(this); + this.on('specialkey', function(f, e){ + if(e.getKey() == e.ENTER){ + this.onTrigger2Click(); + } + }, this); + }, + + validationEvent:false, + validateOnBlur:false, + trigger1Class:'x-form-clear-trigger', + trigger2Class:'x-form-search-trigger', + hideTrigger1:true, + width:180, + hasSearch : false, + paramName : 'query', + + onTrigger1Click : function(){ + if(this.hasSearch){ + this.el.dom.value = ''; + var o = {start: 0}; + this.store.baseParams = this.store.baseParams || {}; + this.store.baseParams[this.paramName] = ''; + this.store.reload({params:o}); + this.triggers[0].hide(); + this.hasSearch = false; + } + }, + + onTrigger2Click : function(){ + var v = this.getRawValue(); + if(v.length < 1){ + this.onTrigger1Click(); + return; + } + var o = {start: 0}; + this.store.baseParams = this.store.baseParams || {}; + this.store.baseParams[this.paramName] = v; + this.store.reload({params:o}); + this.hasSearch = true; + this.triggers[0].show(); + } +});Ext.ns('Ext.ux.form'); + +/** + * @class Ext.ux.form.SelectBox + * @extends Ext.form.ComboBox + *

    Makes a ComboBox more closely mimic an HTML SELECT. Supports clicking and dragging + * through the list, with item selection occurring when the mouse button is released. + * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable} + * on inner elements. Re-enabling editable after calling this will NOT work.

    + * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392 + * @history 2007-07-08 jvs + * Slight mods for Ext 2.0 + * @xtype selectbox + */ +Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, { + constructor: function(config){ + this.searchResetDelay = 1000; + config = config || {}; + config = Ext.apply(config || {}, { + editable: false, + forceSelection: true, + rowHeight: false, + lastSearchTerm: false, + triggerAction: 'all', + mode: 'local' + }); + + Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments); + + this.lastSelectedIndex = this.selectedIndex || 0; + }, + + initEvents : function(){ + Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments); + // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE + this.el.on('keydown', this.keySearch, this, true); + this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this); + }, + + keySearch : function(e, target, options) { + var raw = e.getKey(); + var key = String.fromCharCode(raw); + var startIndex = 0; + + if( !this.store.getCount() ) { + return; + } + + switch(raw) { + case Ext.EventObject.HOME: + e.stopEvent(); + this.selectFirst(); + return; + + case Ext.EventObject.END: + e.stopEvent(); + this.selectLast(); + return; + + case Ext.EventObject.PAGEDOWN: + this.selectNextPage(); + e.stopEvent(); + return; + + case Ext.EventObject.PAGEUP: + this.selectPrevPage(); + e.stopEvent(); + return; + } + + // skip special keys other than the shift key + if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) { + return; + } + if( this.lastSearchTerm == key ) { + startIndex = this.lastSelectedIndex; + } + this.search(this.displayField, key, startIndex); + this.cshTask.delay(this.searchResetDelay); + }, + + onRender : function(ct, position) { + this.store.on('load', this.calcRowsPerPage, this); + Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments); + if( this.mode == 'local' ) { + this.calcRowsPerPage(); + } + }, + + onSelect : function(record, index, skipCollapse){ + if(this.fireEvent('beforeselect', this, record, index) !== false){ + this.setValue(record.data[this.valueField || this.displayField]); + if( !skipCollapse ) { + this.collapse(); + } + this.lastSelectedIndex = index + 1; + this.fireEvent('select', this, record, index); + } + }, + + render : function(ct) { + Ext.ux.form.SelectBox.superclass.render.apply(this, arguments); + if( Ext.isSafari ) { + this.el.swallowEvent('mousedown', true); + } + this.el.unselectable(); + this.innerList.unselectable(); + this.trigger.unselectable(); + this.innerList.on('mouseup', function(e, target, options) { + if( target.id && target.id == this.innerList.id ) { + return; + } + this.onViewClick(); + }, this); + + this.innerList.on('mouseover', function(e, target, options) { + if( target.id && target.id == this.innerList.id ) { + return; + } + this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1; + this.cshTask.delay(this.searchResetDelay); + }, this); + + this.trigger.un('click', this.onTriggerClick, this); + this.trigger.on('mousedown', function(e, target, options) { + e.preventDefault(); + this.onTriggerClick(); + }, this); + + this.on('collapse', function(e, target, options) { + Ext.getDoc().un('mouseup', this.collapseIf, this); + }, this, true); + + this.on('expand', function(e, target, options) { + Ext.getDoc().on('mouseup', this.collapseIf, this); + }, this, true); + }, + + clearSearchHistory : function() { + this.lastSelectedIndex = 0; + this.lastSearchTerm = false; + }, + + selectFirst : function() { + this.focusAndSelect(this.store.data.first()); + }, + + selectLast : function() { + this.focusAndSelect(this.store.data.last()); + }, + + selectPrevPage : function() { + if( !this.rowHeight ) { + return; + } + var index = Math.max(this.selectedIndex-this.rowsPerPage, 0); + this.focusAndSelect(this.store.getAt(index)); + }, + + selectNextPage : function() { + if( !this.rowHeight ) { + return; + } + var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1); + this.focusAndSelect(this.store.getAt(index)); + }, + + search : function(field, value, startIndex) { + field = field || this.displayField; + this.lastSearchTerm = value; + var index = this.store.find.apply(this.store, arguments); + if( index !== -1 ) { + this.focusAndSelect(index); + } + }, + + focusAndSelect : function(record) { + var index = typeof record === 'number' ? record : this.store.indexOf(record); + this.select(index, this.isExpanded()); + this.onSelect(this.store.getAt(record), index, this.isExpanded()); + }, + + calcRowsPerPage : function() { + if( this.store.getCount() ) { + this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight(); + this.rowsPerPage = this.maxHeight / this.rowHeight; + } else { + this.rowHeight = false; + } + } + +}); + +Ext.reg('selectbox', Ext.ux.form.SelectBox); + +//backwards compat +Ext.ux.SelectBox = Ext.ux.form.SelectBox; +/** + * @class Ext.ux.SliderTip + * @extends Ext.Tip + * Simple plugin for using an Ext.Tip with a slider to show the slider value + */ +Ext.ux.SliderTip = Ext.extend(Ext.Tip, { + minWidth: 10, + offsets : [0, -10], + init : function(slider){ + slider.on('dragstart', this.onSlide, this); + slider.on('drag', this.onSlide, this); + slider.on('dragend', this.hide, this); + slider.on('destroy', this.destroy, this); + }, + + onSlide : function(slider){ + this.show(); + this.body.update(this.getText(slider)); + this.doAutoWidth(); + this.el.alignTo(slider.thumb, 'b-t?', this.offsets); + }, + + getText : function(slider){ + return String(slider.getValue()); + } +}); +Ext.ux.SlidingPager = Ext.extend(Object, { + init : function(pbar){ + Ext.each(pbar.items.getRange(2,6), function(c){ + c.hide(); + }); + var slider = new Ext.Slider({ + width: 114, + minValue: 1, + maxValue: 1, + plugins: new Ext.ux.SliderTip({ + getText : function(s){ + return String.format('Page {0} of {1}', s.value, s.maxValue); + } + }), + listeners: { + changecomplete: function(s, v){ + pbar.changePage(v); + } + } + }); + pbar.insert(5, slider); + pbar.on({ + change: function(pb, data){ + slider.maxValue = data.pages; + slider.setValue(data.activePage); + }, + beforedestroy: function(){ + slider.destroy(); + } + }); + } +});Ext.ns('Ext.ux.form'); + +/** + * @class Ext.ux.form.SpinnerField + * @extends Ext.form.NumberField + * Creates a field utilizing Ext.ux.Spinner + * @xtype spinnerfield + */ +Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, { + deferHeight: true, + autoSize: Ext.emptyFn, + onBlur: Ext.emptyFn, + adjustSize: Ext.BoxComponent.prototype.adjustSize, + + constructor: function(config) { + var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass'); + + var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig); + + var plugins = config.plugins + ? (Ext.isArray(config.plugins) + ? config.plugins.push(spl) + : [config.plugins, spl]) + : spl; + + Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins})); + }, + + onShow: function(){ + if (this.wrap) { + this.wrap.dom.style.display = ''; + this.wrap.dom.style.visibility = 'visible'; + } + }, + + onHide: function(){ + this.wrap.dom.style.display = 'none'; + }, + + // private + getResizeEl: function(){ + return this.wrap; + }, + + // private + getPositionEl: function(){ + return this.wrap; + }, + + // private + alignErrorIcon: function(){ + if (this.wrap) { + this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); + } + }, + + validateBlur: function(){ + return true; + } +}); + +Ext.reg('spinnerfield', Ext.ux.form.SpinnerField); + +//backwards compat +Ext.form.SpinnerField = Ext.ux.form.SpinnerField; +/** + * @class Ext.ux.Spinner + * @extends Ext.util.Observable + * Creates a Spinner control utilized by Ext.ux.form.SpinnerField + */ +Ext.ux.Spinner = Ext.extend(Ext.util.Observable, { + incrementValue: 1, + alternateIncrementValue: 5, + triggerClass: 'x-form-spinner-trigger', + splitterClass: 'x-form-spinner-splitter', + alternateKey: Ext.EventObject.shiftKey, + defaultValue: 0, + accelerate: false, + + constructor: function(config){ + Ext.ux.Spinner.superclass.constructor.call(this, config); + Ext.apply(this, config); + this.mimicing = false; + }, + + init: function(field){ + this.field = field; + + field.afterMethod('onRender', this.doRender, this); + field.afterMethod('onEnable', this.doEnable, this); + field.afterMethod('onDisable', this.doDisable, this); + field.afterMethod('afterRender', this.doAfterRender, this); + field.afterMethod('onResize', this.doResize, this); + field.afterMethod('onFocus', this.doFocus, this); + field.beforeMethod('onDestroy', this.doDestroy, this); + }, + + doRender: function(ct, position){ + var el = this.el = this.field.getEl(); + var f = this.field; + + if (!f.wrap) { + f.wrap = this.wrap = el.wrap({ + cls: "x-form-field-wrap" + }); + } + else { + this.wrap = f.wrap.addClass('x-form-field-wrap'); + } + + this.trigger = this.wrap.createChild({ + tag: "img", + src: Ext.BLANK_IMAGE_URL, + cls: "x-form-trigger " + this.triggerClass + }); + + if (!f.width) { + this.wrap.setWidth(el.getWidth() + this.trigger.getWidth()); + } + + this.splitter = this.wrap.createChild({ + tag: 'div', + cls: this.splitterClass, + style: 'width:13px; height:2px;' + }); + this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show(); + + this.proxy = this.trigger.createProxy('', this.splitter, true); + this.proxy.addClass("x-form-spinner-proxy"); + this.proxy.setStyle('left', '0px'); + this.proxy.setSize(14, 1); + this.proxy.hide(); + this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", { + dragElId: this.proxy.id + }); + + this.initTrigger(); + this.initSpinner(); + }, + + doAfterRender: function(){ + var y; + if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) { + this.el.position(); + this.el.setY(y); + } + }, + + doEnable: function(){ + if (this.wrap) { + this.wrap.removeClass(this.field.disabledClass); + } + }, + + doDisable: function(){ + if (this.wrap) { + this.wrap.addClass(this.field.disabledClass); + this.el.removeClass(this.field.disabledClass); + } + }, + + doResize: function(w, h){ + if (typeof w == 'number') { + this.el.setWidth(this.field.adjustWidth('input', w - this.trigger.getWidth())); + } + this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth()); + }, + + doFocus: function(){ + if (!this.mimicing) { + this.wrap.addClass('x-trigger-wrap-focus'); + this.mimicing = true; + Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, { + delay: 10 + }); + this.el.on('keydown', this.checkTab, this); + } + }, + + // private + checkTab: function(e){ + if (e.getKey() == e.TAB) { + this.triggerBlur(); + } + }, + + // private + mimicBlur: function(e){ + if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) { + this.triggerBlur(); + } + }, + + // private + triggerBlur: function(){ + this.mimicing = false; + Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this); + this.el.un("keydown", this.checkTab, this); + this.field.beforeBlur(); + this.wrap.removeClass('x-trigger-wrap-focus'); + this.field.onBlur.call(this.field); + }, + + initTrigger: function(){ + this.trigger.addClassOnOver('x-form-trigger-over'); + this.trigger.addClassOnClick('x-form-trigger-click'); + }, + + initSpinner: function(){ + this.field.addEvents({ + 'spin': true, + 'spinup': true, + 'spindown': true + }); + + this.keyNav = new Ext.KeyNav(this.el, { + "up": function(e){ + e.preventDefault(); + this.onSpinUp(); + }, + + "down": function(e){ + e.preventDefault(); + this.onSpinDown(); + }, + + "pageUp": function(e){ + e.preventDefault(); + this.onSpinUpAlternate(); + }, + + "pageDown": function(e){ + e.preventDefault(); + this.onSpinDownAlternate(); + }, + + scope: this + }); + + this.repeater = new Ext.util.ClickRepeater(this.trigger, { + accelerate: this.accelerate + }); + this.field.mon(this.repeater, "click", this.onTriggerClick, this, { + preventDefault: true + }); + + this.field.mon(this.trigger, { + mouseover: this.onMouseOver, + mouseout: this.onMouseOut, + mousemove: this.onMouseMove, + mousedown: this.onMouseDown, + mouseup: this.onMouseUp, + scope: this, + preventDefault: true + }); + + this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this); + + this.dd.setXConstraint(0, 0, 10) + this.dd.setYConstraint(1500, 1500, 10); + this.dd.endDrag = this.endDrag.createDelegate(this); + this.dd.startDrag = this.startDrag.createDelegate(this); + this.dd.onDrag = this.onDrag.createDelegate(this); + }, + + onMouseOver: function(){ + if (this.disabled) { + return; + } + var middle = this.getMiddle(); + this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown'; + this.trigger.addClass(this.tmpHoverClass); + }, + + //private + onMouseOut: function(){ + this.trigger.removeClass(this.tmpHoverClass); + }, + + //private + onMouseMove: function(){ + if (this.disabled) { + return; + } + var middle = this.getMiddle(); + if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") || + ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) { + } + }, + + //private + onMouseDown: function(){ + if (this.disabled) { + return; + } + var middle = this.getMiddle(); + this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown'; + this.trigger.addClass(this.tmpClickClass); + }, + + //private + onMouseUp: function(){ + this.trigger.removeClass(this.tmpClickClass); + }, + + //private + onTriggerClick: function(){ + if (this.disabled || this.el.dom.readOnly) { + return; + } + var middle = this.getMiddle(); + var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down'; + this['onSpin' + ud](); + }, + + //private + getMiddle: function(){ + var t = this.trigger.getTop(); + var h = this.trigger.getHeight(); + var middle = t + (h / 2); + return middle; + }, + + //private + //checks if control is allowed to spin + isSpinnable: function(){ + if (this.disabled || this.el.dom.readOnly) { + Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly + return false; + } + return true; + }, + + handleMouseWheel: function(e){ + //disable scrolling when not focused + if (this.wrap.hasClass('x-trigger-wrap-focus') == false) { + return; + } + + var delta = e.getWheelDelta(); + if (delta > 0) { + this.onSpinUp(); + e.stopEvent(); + } + else + if (delta < 0) { + this.onSpinDown(); + e.stopEvent(); + } + }, + + //private + startDrag: function(){ + this.proxy.show(); + this._previousY = Ext.fly(this.dd.getDragEl()).getTop(); + }, + + //private + endDrag: function(){ + this.proxy.hide(); + }, + + //private + onDrag: function(){ + if (this.disabled) { + return; + } + var y = Ext.fly(this.dd.getDragEl()).getTop(); + var ud = ''; + + if (this._previousY > y) { + ud = 'Up'; + } //up + if (this._previousY < y) { + ud = 'Down'; + } //down + if (ud != '') { + this['onSpin' + ud](); + } + + this._previousY = y; + }, + + //private + onSpinUp: function(){ + if (this.isSpinnable() == false) { + return; + } + if (Ext.EventObject.shiftKey == true) { + this.onSpinUpAlternate(); + return; + } + else { + this.spin(false, false); + } + this.field.fireEvent("spin", this); + this.field.fireEvent("spinup", this); + }, + + //private + onSpinDown: function(){ + if (this.isSpinnable() == false) { + return; + } + if (Ext.EventObject.shiftKey == true) { + this.onSpinDownAlternate(); + return; + } + else { + this.spin(true, false); + } + this.field.fireEvent("spin", this); + this.field.fireEvent("spindown", this); + }, + + //private + onSpinUpAlternate: function(){ + if (this.isSpinnable() == false) { + return; + } + this.spin(false, true); + this.field.fireEvent("spin", this); + this.field.fireEvent("spinup", this); + }, + + //private + onSpinDownAlternate: function(){ + if (this.isSpinnable() == false) { + return; + } + this.spin(true, true); + this.field.fireEvent("spin", this); + this.field.fireEvent("spindown", this); + }, + + spin: function(down, alternate){ + var v = parseFloat(this.field.getValue()); + var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue; + (down == true) ? v -= incr : v += incr; + + v = (isNaN(v)) ? this.defaultValue : v; + v = this.fixBoundries(v); + this.field.setRawValue(v); + }, + + fixBoundries: function(value){ + var v = value; + + if (this.field.minValue != undefined && v < this.field.minValue) { + v = this.field.minValue; + } + if (this.field.maxValue != undefined && v > this.field.maxValue) { + v = this.field.maxValue; + } + + return this.fixPrecision(v); + }, + + // private + fixPrecision: function(value){ + var nan = isNaN(value); + if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) { + return nan ? '' : value; + } + return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision)); + }, + + doDestroy: function(){ + if (this.trigger) { + this.trigger.remove(); + } + if (this.wrap) { + this.wrap.remove(); + delete this.field.wrap; + } + + if (this.splitter) { + this.splitter.remove(); + } + + if (this.dd) { + this.dd.unreg(); + this.dd = null; + } + + if (this.proxy) { + this.proxy.remove(); + } + + if (this.repeater) { + this.repeater.purgeListeners(); + } + } +}); + +//backwards compat +Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){ + Ext.apply(this, config); +} +Ext.ux.Spotlight.prototype = { + active : false, + animate : true, + duration: .25, + easing:'easeNone', + + // private + animated : false, + + createElements : function(){ + var bd = Ext.getBody(); + + this.right = bd.createChild({cls:'x-spotlight'}); + this.left = bd.createChild({cls:'x-spotlight'}); + this.top = bd.createChild({cls:'x-spotlight'}); + this.bottom = bd.createChild({cls:'x-spotlight'}); + + this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]); + }, + + show : function(el, callback, scope){ + if(this.animated){ + this.show.defer(50, this, [el, callback, scope]); + return; + } + this.el = Ext.get(el); + if(!this.right){ + this.createElements(); + } + if(!this.active){ + this.all.setDisplayed(''); + this.applyBounds(true, false); + this.active = true; + Ext.EventManager.onWindowResize(this.syncSize, this); + this.applyBounds(false, this.animate, false, callback, scope); + }else{ + this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous + } + }, + + hide : function(callback, scope){ + if(this.animated){ + this.hide.defer(50, this, [callback, scope]); + return; + } + Ext.EventManager.removeResizeListener(this.syncSize, this); + this.applyBounds(true, this.animate, true, callback, scope); + }, + + doHide : function(){ + this.active = false; + this.all.setDisplayed(false); + }, + + syncSize : function(){ + this.applyBounds(false, false); + }, + + applyBounds : function(basePts, anim, doHide, callback, scope){ + + var rg = this.el.getRegion(); + + var dw = Ext.lib.Dom.getViewWidth(true); + var dh = Ext.lib.Dom.getViewHeight(true); + + var c = 0, cb = false; + if(anim){ + cb = { + callback: function(){ + c++; + if(c == 4){ + this.animated = false; + if(doHide){ + this.doHide(); + } + Ext.callback(callback, scope, [this]); + } + }, + scope: this, + duration: this.duration, + easing: this.easing + }; + this.animated = true; + } + + this.right.setBounds( + rg.right, + basePts ? dh : rg.top, + dw - rg.right, + basePts ? 0 : (dh - rg.top), + cb); + + this.left.setBounds( + 0, + 0, + rg.left, + basePts ? 0 : rg.bottom, + cb); + + this.top.setBounds( + basePts ? dw : rg.left, + 0, + basePts ? 0 : dw - rg.left, + rg.top, + cb); + + this.bottom.setBounds( + 0, + rg.bottom, + basePts ? 0 : rg.right, + dh - rg.bottom, + cb); + + if(!anim){ + if(doHide){ + this.doHide(); + } + if(callback){ + Ext.callback(callback, scope, [this]); + } + } + }, + + destroy : function(){ + this.doHide(); + Ext.destroy( + this.right, + this.left, + this.top, + this.bottom); + delete this.el; + delete this.all; + } +}; + +//backwards compat +Ext.Spotlight = Ext.ux.Spotlight;/** + * @class Ext.ux.TabCloseMenu + * @extends Object + * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs. + * + * @ptype tabclosemenu + */ +Ext.ux.TabCloseMenu = function(){ + var tabs, menu, ctxItem; + this.init = function(tp){ + tabs = tp; + tabs.on('contextmenu', onContextMenu); + }; + + function onContextMenu(ts, item, e){ + if(!menu){ // create context menu on first right click + menu = new Ext.menu.Menu({ + items: [{ + id: tabs.id + '-close', + text: 'Close Tab', + handler : function(){ + tabs.remove(ctxItem); + } + },{ + id: tabs.id + '-close-others', + text: 'Close Other Tabs', + handler : function(){ + tabs.items.each(function(item){ + if(item.closable && item != ctxItem){ + tabs.remove(item); + } + }); + } + }]}); + } + ctxItem = item; + var items = menu.items; + items.get(tabs.id + '-close').setDisabled(!item.closable); + var disableOthers = true; + tabs.items.each(function(){ + if(this != item && this.closable){ + disableOthers = false; + return false; + } + }); + items.get(tabs.id + '-close-others').setDisabled(disableOthers); + e.stopEvent(); + menu.showAt(e.getPoint()); + } +}; + +Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu); +Ext.ns('Ext.ux.grid'); + +/** + * @class Ext.ux.grid.TableGrid + * @extends Ext.grid.GridPanel + * A Grid which creates itself from an existing HTML table element. + * @history + * 2007-03-01 Original version by Nige "Animal" White + * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor + * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created - + * The table MUST have some type of size defined for the grid to fill. The container will be + * automatically set to position relative if it isn't already. + * @param {Object} config A config object that sets properties on this grid and has two additional (optional) + * properties: fields and columns which allow for customizing data fields and columns for this grid. + */ +Ext.ux.grid.TableGrid = function(table, config){ + config = config || + {}; + Ext.apply(this, config); + var cf = config.fields || [], ch = config.columns || []; + table = Ext.get(table); + + var ct = table.insertSibling(); + + var fields = [], cols = []; + var headers = table.query("thead th"); + for (var i = 0, h; h = headers[i]; i++) { + var text = h.innerHTML; + var name = 'tcol-' + i; + + fields.push(Ext.applyIf(cf[i] || + {}, { + name: name, + mapping: 'td:nth(' + (i + 1) + ')/@innerHTML' + })); + + cols.push(Ext.applyIf(ch[i] || + {}, { + 'header': text, + 'dataIndex': name, + 'width': h.offsetWidth, + 'tooltip': h.title, + 'sortable': true + })); + } + + var ds = new Ext.data.Store({ + reader: new Ext.data.XmlReader({ + record: 'tbody tr' + }, fields) + }); + + ds.loadData(table.dom); + + var cm = new Ext.grid.ColumnModel(cols); + + if (config.width || config.height) { + ct.setSize(config.width || 'auto', config.height || 'auto'); + } + else { + ct.setWidth(table.getWidth()); + } + + if (config.remove !== false) { + table.remove(); + } + + Ext.applyIf(this, { + 'ds': ds, + 'cm': cm, + 'sm': new Ext.grid.RowSelectionModel(), + autoHeight: true, + autoWidth: false + }); + Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {}); +}; + +Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel); + +//backwards compat +Ext.grid.TableGrid = Ext.ux.grid.TableGrid; + + +Ext.ux.TabScrollerMenu = Ext.extend(Object, { + pageSize : 10, + maxText : 15, + menuPrefixText : 'Items', + constructor : function(config) { + config = config || {}; + Ext.apply(this, config); + }, + init : function(tabPanel) { + Ext.apply(tabPanel, this.tabPanelMethods); + + tabPanel.tabScrollerMenu = this; + var thisRef = this; + + tabPanel.on({ + render : { + scope : tabPanel, + single : true, + fn : function() { + var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this); + tabPanel.createScrollers = newFn; + } + } + }); + }, + // private && sequeneced + createPanelsMenu : function() { + var h = this.stripWrap.dom.offsetHeight; + + //move the right menu item to the left 18px + var rtScrBtn = this.header.dom.firstChild; + Ext.fly(rtScrBtn).applyStyles({ + right : '18px' + }); + + var stripWrap = Ext.get(this.strip.dom.parentNode); + stripWrap.applyStyles({ + 'margin-right' : '36px' + }); + + // Add the new righthand menu + var scrollMenu = this.header.insertFirst({ + cls:'x-tab-tabmenu-right' + }); + scrollMenu.setHeight(h); + scrollMenu.addClassOnOver('x-tab-tabmenu-over'); + scrollMenu.on('click', this.showTabsMenu, this); + + this.scrollLeft.show = this.scrollLeft.show.createSequence(function() { + scrollMenu.show(); + }); + + this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() { + scrollMenu.hide(); + }); + + }, + // public + getPageSize : function() { + return this.pageSize; + }, + // public + setPageSize : function(pageSize) { + this.pageSize = pageSize; + }, + // public + getMaxText : function() { + return this.maxText; + }, + // public + setMaxText : function(t) { + this.maxText = t; + }, + getMenuPrefixText : function() { + return this.menuPrefixText; + }, + setMenuPrefixText : function(t) { + this.menuPrefixText = t; + }, + // private && applied to the tab panel itself. + tabPanelMethods : { + // all execute within the scope of the tab panel + // private + showTabsMenu : function(e) { + if (! this.tabsMenu) { + this.tabsMenu = new Ext.menu.Menu(); + this.on('beforedestroy', this.tabsMenu.destroy, this.tabsMenu); + } + + this.tabsMenu.removeAll(); + + this.generateTabMenuItems(); + + var target = Ext.get(e.getTarget()); + var xy = target.getXY(); + + //Y param + 24 pixels + xy[1] += 24; + + this.tabsMenu.showAt(xy); + }, + // private + generateTabMenuItems : function() { + var curActive = this.getActiveTab(); + var totalItems = this.items.getCount(); + var pageSize = this.tabScrollerMenu.getPageSize(); + + + if (totalItems > pageSize) { + var numSubMenus = Math.floor(totalItems / pageSize); + var remainder = totalItems % pageSize; + + // Loop through all of the items and create submenus in chunks of 10 + for (var i = 0 ; i < numSubMenus; i++) { + var curPage = (i + 1) * pageSize; + var menuItems = []; + + + for (var x = 0; x < pageSize; x++) { + index = x + curPage - pageSize; + var item = this.items.get(index); + menuItems.push(this.autoGenMenuItem(item)); + } + + this.tabsMenu.add({ + text : this.tabScrollerMenu.getMenuPrefixText() + ' ' + (curPage - pageSize + 1) + ' - ' + curPage, + menu : menuItems + }); + + } + // remaining items + if (remainder > 0) { + var start = numSubMenus * pageSize; + menuItems = []; + for (var i = start ; i < totalItems; i ++ ) { + var item = this.items.get(i); + menuItems.push(this.autoGenMenuItem(item)); + } + + + this.tabsMenu.add({ + text : this.tabScrollerMenu.menuPrefixText + ' ' + (start + 1) + ' - ' + (start + menuItems.length), + menu : menuItems + }); + + + } + } + else { + this.items.each(function(item) { + if (item.id != curActive.id && ! item.hidden) { + menuItems.push(this.autoGenMenuItem(item)); + } + }, this); + } + }, + // private + autoGenMenuItem : function(item) { + var maxText = this.tabScrollerMenu.getMaxText(); + var text = Ext.util.Format.ellipsis(item.title, maxText); + + return { + text : text, + handler : this.showTabFromMenu, + scope : this, + disabled : item.disabled, + tabToShow : item, + iconCls : item.iconCls + } + + }, + // private + showTabFromMenu : function(menuItem) { + this.setActiveTab(menuItem.tabToShow); + } + } +}); +Ext.ns('Ext.ux.tree'); + +/** + * @class Ext.ux.tree.XmlTreeLoader + * @extends Ext.tree.TreeLoader + *

    A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s. + * Any text value included as a text node in the XML will be added to the parent node as an attribute + * called innerText. Also, the tag name of each XML node will be added to the tree node as + * an attribute called tagName.

    + *

    By default, this class expects that your source XML will provide the necessary attributes on each + * node as expected by the {@link Ext.tree.TreePanel} to display and load properly. However, you can + * provide your own custom processing of node attributes by overriding the {@link #processNode} method + * and modifying the attributes as needed before they are used to create the associated TreeNode.

    + * @constructor + * Creates a new XmlTreeloader. + * @param {Object} config A config object containing config properties. + */ +Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, { + /** + * @property XML_NODE_ELEMENT + * XML element node (value 1, read-only) + * @type Number + */ + XML_NODE_ELEMENT : 1, + /** + * @property XML_NODE_TEXT + * XML text node (value 3, read-only) + * @type Number + */ + XML_NODE_TEXT : 3, + + // private override + processResponse : function(response, node, callback){ + var xmlData = response.responseXML; + var root = xmlData.documentElement || xmlData; + + try{ + node.beginUpdate(); + node.appendChild(this.parseXml(root)); + node.endUpdate(); + + if(typeof callback == "function"){ + callback(this, node); + } + }catch(e){ + this.handleFailure(response); + } + }, + + // private + parseXml : function(node) { + var nodes = []; + Ext.each(node.childNodes, function(n){ + if(n.nodeType == this.XML_NODE_ELEMENT){ + var treeNode = this.createNode(n); + if(n.childNodes.length > 0){ + var child = this.parseXml(n); + if(typeof child == 'string'){ + treeNode.attributes.innerText = child; + }else{ + treeNode.appendChild(child); + } + } + nodes.push(treeNode); + } + else if(n.nodeType == this.XML_NODE_TEXT){ + var text = n.nodeValue.trim(); + if(text.length > 0){ + return nodes = text; + } + } + }, this); + + return nodes; + }, + + // private override + createNode : function(node){ + var attr = { + tagName: node.tagName + }; + + Ext.each(node.attributes, function(a){ + attr[a.nodeName] = a.nodeValue; + }); + + this.processAttributes(attr); + + return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr); + }, + + /* + * Template method intended to be overridden by subclasses that need to provide + * custom attribute processing prior to the creation of each TreeNode. This method + * will be passed a config object containing existing TreeNode attribute name/value + * pairs which can be modified as needed directly (no need to return the object). + */ + processAttributes: Ext.emptyFn +}); + +//backwards compat +Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;