/*!
 * Ext JS Library 3.3.1
 * Copyright(c) 2006-2010 Sencha Inc.
 * licensing@sencha.com
 * http://www.sencha.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(), visibleHeight = this.scroller.dom.clientHeight; return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh); }, getVisibleRows: function(){ var count = this.getVisibleRowCount(), sc = this.scroller.dom.scrollTop, 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, rh = this.getStyleRowHeight(), vr = this.getVisibleRows(), tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;', // buffers 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), 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(); } }, onRemove : function(ds, record, index, isUpdate){ Ext.ux.grid.BufferView.superclass.onRemove.apply(this, arguments); if(isUpdate !== true){ this.update(); } }, doUpdate: function(){ if (this.getVisibleRowCount() > 0) { var g = this.grid, cm = g.colModel, ds = g.store, cs = this.getColumnData(), vr = this.getVisibleRows(), row; for (var i = vr.first; i <= vr.last; i++) { // if row is NOT rendered and is visible, render it if(!this.isRowRendered(i) && (row = this.getRow(i))){ var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true); row.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 = ''; } } } }, removeTask: function(name){ var task = this[name]; if(task && task.cancel){ task.cancel(); this[name] = null; } }, destroy : function(){ this.removeTask('cleanTask'); this.removeTask('renderTask'); Ext.ux.grid.BufferView.superclass.destroy.call(this); }, 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 Ext.grid.Column *

A Column subclass which renders a checkbox in each column cell which toggles the truthiness of the associated data field on click.

*

Note. As of ExtJS 3.3 this no longer has to be configured as a plugin of the GridPanel.

*

Example usage:

*

var cm = new Ext.grid.ColumnModel([{
       header: 'Foo',
       ...
    },{
       xtype: 'checkcolumn',
       header: 'Indoor?',
       dataIndex: 'indoor',
       width: 55
    }
]);

// create the grid
var grid = new Ext.grid.EditorGridPanel({
    ...
    colModel: cm,
    ...
});
 * 
* In addition to toggling 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 = Ext.extend(Ext.grid.Column, { /** * @private * Process and refire events routed from the GridView's processEvent method. */ processEvent : function(name, e, grid, rowIndex, colIndex){ if (name == 'mousedown') { var record = grid.store.getAt(rowIndex); record.set(this.dataIndex, !record.data[this.dataIndex]); return false; // Cancel row selection. } else { return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments); } }, renderer : function(v, p, record){ p.css += ' x-grid3-check-col-td'; return String.format('
 
', v ? '-on' : ''); }, // Deprecate use as a plugin. Remove in 4.0 init: Ext.emptyFn }); // register ptype. Deprecate. Remove in 4.0 Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn); // backwards compat. Remove in 4.0 Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn; // register Column xtype Ext.grid.Column.types.checkcolumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.grid'); Ext.ux.grid.ColumnHeaderGroup = Ext.extend(Ext.util.Observable, { constructor: function(config){ this.config = config; }, init: function(grid){ Ext.applyIf(grid.colModel, this.config); Ext.apply(grid.getView(), this.viewConfig); }, viewConfig: { initTemplates: function(){ this.constructor.prototype.initTemplates.apply(this, arguments); var ts = this.templates || {}; if(!ts.gcell){ ts.gcell = new Ext.XTemplate('', '
', this.grid.enableHdMenu ? '' : '', '{value}
'); } this.templates = ts; this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", ""); }, renderHeaders: function(){ var ts = this.templates, headers = [], cm = this.cm, rows = cm.rows, tstyle = 'width:' + this.getTotalWidth() + ';'; for(var row = 0, rlen = rows.length; row < rlen; row++){ var r = rows[row], cells = []; for(var i = 0, gcol = 0, len = r.length; i < len; i++){ var group = r[i]; group.colspan = group.colspan || 1; var id = this.getColumnId(group.dataIndex ? cm.findColumnIndex(group.dataIndex) : gcol), gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol); cells[i] = ts.gcell.apply({ cls: 'ux-grid-hd-group-cell', id: id, row: row, style: 'width:' + gs.width + ';' + (gs.hidden ? 'display:none;' : '') + (group.align ? 'text-align:' + group.align + ';' : ''), tooltip: group.tooltip ? (Ext.QuickTips.isEnabled() ? 'ext:qtip' : 'title') + '="' + group.tooltip + '"' : '', istyle: group.align == 'right' ? 'padding-right:16px' : '', btn: this.grid.enableHdMenu && group.header, value: group.header || ' ' }); gcol += group.colspan; } headers[row] = ts.header.apply({ tstyle: tstyle, cells: cells.join('') }); } headers.push(this.constructor.prototype.renderHeaders.apply(this, arguments)); return headers.join(''); }, onColumnWidthUpdated: function(){ this.constructor.prototype.onColumnWidthUpdated.apply(this, arguments); Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this); }, onAllColumnWidthsUpdated: function(){ this.constructor.prototype.onAllColumnWidthsUpdated.apply(this, arguments); Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this); }, onColumnHiddenUpdated: function(){ this.constructor.prototype.onColumnHiddenUpdated.apply(this, arguments); Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this); }, getHeaderCell: function(index){ return this.mainHd.query(this.cellSelector)[index]; }, findHeaderCell: function(el){ return el ? this.fly(el).findParent('td.x-grid3-hd', this.cellSelectorDepth) : false; }, findHeaderIndex: function(el){ var cell = this.findHeaderCell(el); return cell ? this.getCellIndex(cell) : false; }, updateSortIcon: function(col, dir){ var sc = this.sortClasses, hds = this.mainHd.select(this.cellSelector).removeClass(sc); hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]); }, handleHdDown: function(e, t){ var el = Ext.get(t); if(el.hasClass('x-grid3-hd-btn')){ e.stopEvent(); var hd = this.findHeaderCell(t); Ext.fly(hd).addClass('x-grid3-hd-menu-open'); var index = this.getCellIndex(hd); this.hdCtxIndex = index; var ms = this.hmenu.items, cm = this.cm; ms.get('asc').setDisabled(!cm.isSortable(index)); ms.get('desc').setDisabled(!cm.isSortable(index)); this.hmenu.on('hide', function(){ Ext.fly(hd).removeClass('x-grid3-hd-menu-open'); }, this, { single: true }); this.hmenu.show(t, 'tl-bl?'); }else if(el.hasClass('ux-grid-hd-group-cell') || Ext.fly(t).up('.ux-grid-hd-group-cell')){ e.stopEvent(); } }, handleHdMove: function(e, t){ var hd = this.findHeaderCell(this.activeHdRef); if(hd && !this.headersDisabled && !Ext.fly(hd).hasClass('ux-grid-hd-group-cell')){ var hw = this.splitHandleWidth || 5, r = this.activeHdRegion, x = e.getPageX(), ss = hd.style, cur = ''; if(this.grid.enableColumnResize !== false){ if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex - 1)){ cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize // not // always // supported }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){ cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize'; } } ss.cursor = cur; } }, handleHdOver: function(e, t){ var hd = this.findHeaderCell(t); if(hd && !this.headersDisabled){ this.activeHdRef = t; this.activeHdIndex = this.getCellIndex(hd); var fly = this.fly(hd); this.activeHdRegion = fly.getRegion(); if(!(this.cm.isMenuDisabled(this.activeHdIndex) || fly.hasClass('ux-grid-hd-group-cell'))){ fly.addClass('x-grid3-hd-over'); this.activeHdBtn = fly.child('.x-grid3-hd-btn'); if(this.activeHdBtn){ this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight - 1) + 'px'; } } } }, handleHdOut: function(e, t){ var hd = this.findHeaderCell(t); if(hd && (!Ext.isIE || !e.within(hd, true))){ this.activeHdRef = null; this.fly(hd).removeClass('x-grid3-hd-over'); hd.style.cursor = ''; } }, handleHdMenuClick: function(item){ var index = this.hdCtxIndex, cm = this.cm, ds = this.ds, id = item.getItemId(); switch(id){ case 'asc': ds.sort(cm.getDataIndex(index), 'ASC'); break; case 'desc': ds.sort(cm.getDataIndex(index), 'DESC'); break; default: if(id.substr(0, 6) == 'group-'){ var i = id.split('-'), row = parseInt(i[1], 10), col = parseInt(i[2], 10), r = this.cm.rows[row], group, gcol = 0; for(var i = 0, len = r.length; i < len; i++){ group = r[i]; if(col >= gcol && col < gcol + group.colspan){ break; } gcol += group.colspan; } if(item.checked){ var max = cm.getColumnsBy(this.isHideableColumn, this).length; for(var i = gcol, len = gcol + group.colspan; i < len; i++){ if(!cm.isHidden(i)){ max--; } } if(max < 1){ this.onDenyColumnHide(); return false; } } for(var i = gcol, len = gcol + group.colspan; i < len; i++){ if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){ cm.setHidden(i, item.checked); } } }else if(id.substr(0, 4) == 'col-'){ index = cm.getIndexById(id.substr(4)); if(index != -1){ if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){ this.onDenyColumnHide(); return false; } cm.setHidden(index, item.checked); } } if(id.substr(0, 6) == 'group-' || id.substr(0, 4) == 'col-'){ item.checked = !item.checked; if(item.menu){ var updateChildren = function(menu){ menu.items.each(function(childItem){ if(!childItem.disabled){ childItem.setChecked(item.checked, false); if(childItem.menu){ updateChildren(childItem.menu); } } }); } updateChildren(item.menu); } var parentMenu = item, parentItem; while(parentMenu = parentMenu.parentMenu){ if(!parentMenu.parentMenu || !(parentItem = parentMenu.parentMenu.items.get(parentMenu.getItemId())) || !parentItem.setChecked){ break; } var checked = parentMenu.items.findIndexBy(function(m){ return m.checked; }) >= 0; parentItem.setChecked(checked, true); } item.checked = !item.checked; } } return true; }, beforeColMenuShow: function(){ var cm = this.cm, rows = this.cm.rows; this.colMenu.removeAll(); for(var col = 0, clen = cm.getColumnCount(); col < clen; col++){ var menu = this.colMenu, title = cm.getColumnHeader(col), text = []; if(cm.config[col].fixed !== true && cm.config[col].hideable !== false){ for(var row = 0, rlen = rows.length; row < rlen; row++){ var r = rows[row], group, gcol = 0; for(var i = 0, len = r.length; i < len; i++){ group = r[i]; if(col >= gcol && col < gcol + group.colspan){ break; } gcol += group.colspan; } if(group && group.header){ if(cm.hierarchicalColMenu){ var gid = 'group-' + row + '-' + gcol, item = menu.items ? menu.getComponent(gid) : null, submenu = item ? item.menu : null; if(!submenu){ submenu = new Ext.menu.Menu({ itemId: gid }); submenu.on("itemclick", this.handleHdMenuClick, this); var checked = false, disabled = true; for(var c = gcol, lc = gcol + group.colspan; c < lc; c++){ if(!cm.isHidden(c)){ checked = true; } if(cm.config[c].hideable !== false){ disabled = false; } } menu.add({ itemId: gid, text: group.header, menu: submenu, hideOnClick: false, checked: checked, disabled: disabled }); } menu = submenu; }else{ text.push(group.header); } } } text.push(title); menu.add(new Ext.menu.CheckItem({ itemId: "col-" + cm.getColumnId(col), text: text.join(' '), checked: !cm.isHidden(col), hideOnClick: false, disabled: cm.config[col].hideable === false })); } } }, afterRenderUI: function(){ this.constructor.prototype.afterRenderUI.apply(this, arguments); Ext.apply(this.columnDrop, Ext.ux.grid.ColumnHeaderGroup.prototype.columnDropConfig); Ext.apply(this.splitZone, Ext.ux.grid.ColumnHeaderGroup.prototype.splitZoneConfig); } }, splitZoneConfig: { allowHeaderDrag: function(e){ return !e.getTarget(null, null, true).hasClass('ux-grid-hd-group-cell'); } }, columnDropConfig: { getTargetFromEvent: function(e){ var t = Ext.lib.Event.getTarget(e); return this.view.findHeaderCell(t); }, positionIndicator: function(h, n, e){ var data = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e); if(data === false){ return false; } var px = data.px + this.proxyOffsets[0]; this.proxyTop.setLeftTop(px, data.r.top + this.proxyOffsets[1]); this.proxyTop.show(); this.proxyBottom.setLeftTop(px, data.r.bottom); this.proxyBottom.show(); return data.pt; }, onNodeDrop: function(n, dd, e, data){ var h = data.header; if(h != n){ var d = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e); if(d === false){ return false; } var cm = this.grid.colModel, right = d.oldIndex < d.newIndex, rows = cm.rows; for(var row = d.row, rlen = rows.length; row < rlen; row++){ var r = rows[row], len = r.length, fromIx = 0, span = 1, toIx = len; for(var i = 0, gcol = 0; i < len; i++){ var group = r[i]; if(d.oldIndex >= gcol && d.oldIndex < gcol + group.colspan){ fromIx = i; } if(d.oldIndex + d.colspan - 1 >= gcol && d.oldIndex + d.colspan - 1 < gcol + group.colspan){ span = i - fromIx + 1; } if(d.newIndex >= gcol && d.newIndex < gcol + group.colspan){ toIx = i; } gcol += group.colspan; } var groups = r.splice(fromIx, span); rows[row] = r.splice(0, toIx - (right ? span : 0)).concat(groups).concat(r); } for(var c = 0; c < d.colspan; c++){ var oldIx = d.oldIndex + (right ? 0 : c), newIx = d.newIndex + (right ? -1 : c); cm.moveColumn(oldIx, newIx); this.grid.fireEvent("columnmove", oldIx, newIx); } return true; } return false; } }, getGroupStyle: function(group, gcol){ var width = 0, hidden = true; for(var i = gcol, len = gcol + group.colspan; i < len; i++){ if(!this.cm.isHidden(i)){ var cw = this.cm.getColumnWidth(i); if(typeof cw == 'number'){ width += cw; } hidden = false; } } return { width: (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? width : Math.max(width - this.borderWidth, 0)) + 'px', hidden: hidden }; }, updateGroupStyles: function(col){ var tables = this.mainHd.query('.x-grid3-header-offset > table'), tw = this.getTotalWidth(), rows = this.cm.rows; for(var row = 0; row < tables.length; row++){ tables[row].style.width = tw; if(row < rows.length){ var cells = tables[row].firstChild.firstChild.childNodes; for(var i = 0, gcol = 0; i < cells.length; i++){ var group = rows[row][i]; if((typeof col != 'number') || (col >= gcol && col < gcol + group.colspan)){ var gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol); cells[i].style.width = gs.width; cells[i].style.display = gs.hidden ? 'none' : ''; } gcol += group.colspan; } } } }, getGroupRowIndex: function(el){ if(el){ var m = el.className.match(this.hrowRe); if(m && m[1]){ return parseInt(m[1], 10); } } return this.cm.rows.length; }, getGroupSpan: function(row, col){ if(row < 0){ return { col: 0, colspan: this.cm.getColumnCount() }; } var r = this.cm.rows[row]; if(r){ for(var i = 0, gcol = 0, len = r.length; i < len; i++){ var group = r[i]; if(col >= gcol && col < gcol + group.colspan){ return { col: gcol, colspan: group.colspan }; } gcol += group.colspan; } return { col: gcol, colspan: 0 }; } return { col: col, colspan: 1 }; }, getDragDropData: function(h, n, e){ if(h.parentNode != n.parentNode){ return false; } var cm = this.grid.colModel, x = Ext.lib.Event.getPageX(e), r = Ext.lib.Dom.getRegion(n.firstChild), px, pt; if((r.right - x) <= (r.right - r.left) / 2){ px = r.right + this.view.borderWidth; pt = "after"; }else{ px = r.left; pt = "before"; } var oldIndex = this.view.getCellIndex(h), newIndex = this.view.getCellIndex(n); if(cm.isFixed(newIndex)){ return false; } var row = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupRowIndex.call(this.view, h), oldGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, oldIndex), newGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, newIndex), oldIndex = oldGroup.col; newIndex = newGroup.col + (pt == "after" ? newGroup.colspan : 0); if(newIndex >= oldGroup.col && newIndex <= oldGroup.col + oldGroup.colspan){ return false; } var parentGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row - 1, oldIndex); if(newIndex < parentGroup.col || newIndex > parentGroup.col + parentGroup.colspan){ return false; } return { r: r, px: px, pt: pt, row: row, oldIndex: oldIndex, newIndex: newIndex, colspan: oldGroup.colspan }; } });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{ if(proxy.dom.parentNode !== view.el.dom){ view.el.dom.appendChild(proxy.dom); } 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.createFileInput(); 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.bindListeners(); this.resizeEl = this.positionEl = this.wrap; }, bindListeners: function(){ this.fileInput.on({ scope: this, mouseenter: function() { this.button.addClass(['x-btn-over','x-btn-focus']) }, mouseleave: function(){ this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click']) }, mousedown: function(){ this.button.addClass('x-btn-click') }, mouseup: function(){ this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click']) }, change: function(){ var v = this.fileInput.dom.value; this.setValue(v); this.fireEvent('fileselected', this, v); } }); }, createFileInput : function() { this.fileInput = this.wrap.createChild({ id: this.getFileInputId(), name: this.name||this.getId(), cls: 'x-form-file', tag: 'input', type: 'file', size: 1 }); }, reset : function(){ this.fileInput.remove(); this.createFileInput(); this.bindListeners(); Ext.ux.form.FileUploadField.superclass.reset.call(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); }, onDisable: function(){ Ext.ux.form.FileUploadField.superclass.onDisable.call(this); this.doDisable(true); }, onEnable: function(){ Ext.ux.form.FileUploadField.superclass.onEnable.call(this); this.doDisable(false); }, // private doDisable: function(disabled){ this.fileInput.dom.disabled = disabled; this.button.setDisabled(disabled); }, // private preFocus : Ext.emptyFn, // 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;
    /** * @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.namespace('Ext.ux.grid'); /** * @class Ext.ux.grid.GridFilters * @extends Ext.util.Observable *

    GridFilter is a plugin (ptype='gridfilters') for grids that * allow for a slightly more robust representation of filtering than what is * provided by the default store.

    *

    Filtering is adjusted by the user using the grid's column header menu * (this menu can be disabled through configuration). Through this menu users * can configure, enable, and disable filters for each column.

    *

    Features:

    *
      *
    • Filtering implementations : *
      * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can * be backed by a Ext.data.Store), and Boolean. Additional custom filter types * and menus are easily created by extending Ext.ux.grid.filter.Filter. *
    • *
    • Graphical indicators : *
      * Columns that are filtered have {@link #filterCls a configurable css class} * applied to the column headers. *
    • *
    • Paging : *
      * If specified as a plugin to the grid's configured PagingToolbar, the current page * will be reset to page 1 whenever you update the filters. *
    • *
    • Automatic Reconfiguration : *
      * Filters automatically reconfigure when the grid 'reconfigure' event fires. *
    • *
    • Stateful : * Filter information will be persisted across page loads by specifying a * stateId in the Grid configuration. *
      * The filter collection binds to the * {@link Ext.grid.GridPanel#beforestaterestore beforestaterestore} * and {@link Ext.grid.GridPanel#beforestatesave beforestatesave} * events in order to be stateful. *
    • *
    • Grid Changes : *
        *
      • A filters property is added to the grid pointing to * this plugin.
      • *
      • A filterupdate event is added to the grid and is * fired upon onStateChange completion.
      • *
    • *
    • Server side code examples : *
    • *
    *

    Example usage:

    *
    
    var store = new Ext.data.GroupingStore({
        ...
    });
    
    var filters = new Ext.ux.grid.GridFilters({
        autoReload: false, //don't reload automatically
        local: true, //only filter locally
        // filters may be configured through the plugin,
        // or in the column definition within the column model configuration
        filters: [{
            type: 'numeric',
            dataIndex: 'id'
        }, {
            type: 'string',
            dataIndex: 'name'
        }, {
            type: 'numeric',
            dataIndex: 'price'
        }, {
            type: 'date',
            dataIndex: 'dateAdded'
        }, {
            type: 'list',
            dataIndex: 'size',
            options: ['extra small', 'small', 'medium', 'large', 'extra large'],
            phpMode: true
        }, {
            type: 'boolean',
            dataIndex: 'visible'
        }]
    });
    var cm = new Ext.grid.ColumnModel([{
        ...
    }]);
    
    var grid = new Ext.grid.GridPanel({
         ds: store,
         cm: cm,
         view: new Ext.grid.GroupingView(),
         plugins: [filters],
         height: 400,
         width: 700,
         bbar: new Ext.PagingToolbar({
             store: store,
             pageSize: 15,
             plugins: [filters] //reset page to page 1 if filters change
         })
     });
    
    store.load({params: {start: 0, limit: 15}});
    
    // a filters property is added to the grid
    grid.filters
     * 
    */ Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {
    /** * @cfg {Boolean} autoReload * Defaults to true, reloading the datasource when a filter change happens. * Set this to false to prevent the datastore from being reloaded if there * are changes to the filters. See {@link updateBuffer}. */ autoReload : true,
    /** * @cfg {Boolean} encode * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to * encode the filter query parameter sent with a remote request. * Defaults to false. */
    /** * @cfg {Array} filters * An Array of filters config objects. Refer to each filter type class for * configuration details specific to each filter type. Filters for Strings, * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters * available. */
    /** * @cfg {String} filterCls * The css class to be applied to column headers with active filters. * Defaults to 'ux-filterd-column'. */ filterCls : 'ux-filtered-column',
    /** * @cfg {Boolean} local * true to use Ext.data.Store filter functions (local filtering) * instead of the default (false) server side filtering. */ local : false,
    /** * @cfg {String} menuFilterText * defaults to 'Filters'. */ menuFilterText : 'Filters',
    /** * @cfg {String} paramPrefix * The url parameter prefix for the filters. * Defaults to 'filter'. */ paramPrefix : 'filter',
    /** * @cfg {Boolean} showMenu * Defaults to true, including a filter submenu in the default header menu. */ showMenu : true,
    /** * @cfg {String} stateId * Name of the value to be used to store state information. */ stateId : undefined,
    /** * @cfg {Integer} updateBuffer * Number of milliseconds to defer store updates since the last filter change. */ updateBuffer : 500, /** @private */ constructor : function (config) { config = config || {}; this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this); this.filters = new Ext.util.MixedCollection(); this.filters.getKey = function (o) { return o ? o.dataIndex : null; }; this.addFilters(config.filters); delete config.filters; Ext.apply(this, config); }, /** @private */ init : function (grid) { if (grid instanceof Ext.grid.GridPanel) { this.grid = grid; this.bindStore(this.grid.getStore(), true); // assumes no filters were passed in the constructor, so try and use ones from the colModel if(this.filters.getCount() == 0){ this.addFilters(this.grid.getColumnModel()); } this.grid.filters = this; this.grid.addEvents({'filterupdate': true}); grid.on({ scope: this, beforestaterestore: this.applyState, beforestatesave: this.saveState, beforedestroy: this.destroy, reconfigure: this.onReconfigure }); if (grid.rendered){ this.onRender(); } else { grid.on({ scope: this, single: true, render: this.onRender }); } } else if (grid instanceof Ext.PagingToolbar) { this.toolbar = grid; } }, /** * @private * Handler for the grid's beforestaterestore event (fires before the state of the * grid is restored). * @param {Object} grid The grid object * @param {Object} state The hash of state values returned from the StateProvider. */ applyState : function (grid, state) { var key, filter; this.applyingState = true; this.clearFilters(); if (state.filters) { for (key in state.filters) { filter = this.filters.get(key); if (filter) { filter.setValue(state.filters[key]); filter.setActive(true); } } } this.deferredUpdate.cancel(); if (this.local) { this.reload(); } delete this.applyingState; delete state.filters; },
    /** * Saves the state of all active filters * @param {Object} grid * @param {Object} state * @return {Boolean} */ saveState : function (grid, state) { var filters = {}; this.filters.each(function (filter) { if (filter.active) { filters[filter.dataIndex] = filter.getValue(); } }); return (state.filters = filters); }, /** * @private * Handler called when the grid is rendered */ onRender : function () { this.grid.getView().on('refresh', this.onRefresh, this); this.createMenu(); }, /** * @private * Handler called by the grid 'beforedestroy' event */ destroy : function () { this.removeAll(); this.purgeListeners(); if(this.filterMenu){ Ext.menu.MenuMgr.unregister(this.filterMenu); this.filterMenu.destroy(); this.filterMenu = this.menu.menu = null; } },
    /** * Remove all filters, permanently destroying them. */ removeAll : function () { if(this.filters){ Ext.destroy.apply(Ext, this.filters.items); // remove all items from the collection this.filters.clear(); } },
    /** * Changes the data store bound to this view and refreshes it. * @param {Store} store The store to bind to this view */ bindStore : function(store, initial){ if(!initial && this.store){ if (this.local) { store.un('load', this.onLoad, this); } else { store.un('beforeload', this.onBeforeLoad, this); } } if(store){ if (this.local) { store.on('load', this.onLoad, this); } else { store.on('beforeload', this.onBeforeLoad, this); } } this.store = store; }, /** * @private * Handler called when the grid reconfigure event fires */ onReconfigure : function () { this.bindStore(this.grid.getStore()); this.store.clearFilter(); this.removeAll(); this.addFilters(this.grid.getColumnModel()); this.updateColumnHeadings(); }, createMenu : function () { var view = this.grid.getView(), hmenu = view.hmenu; if (this.showMenu && hmenu) { this.sep = hmenu.addSeparator(); this.filterMenu = new Ext.menu.Menu({ id: this.grid.id + '-filters-menu' }); this.menu = hmenu.add({ checked: false, itemId: 'filters', text: this.menuFilterText, menu: this.filterMenu }); this.menu.on({ scope: this, checkchange: this.onCheckChange, beforecheckchange: this.onBeforeCheck }); hmenu.on('beforeshow', this.onMenu, this); } this.updateColumnHeadings(); }, /** * @private * Get the filter menu from the filters MixedCollection based on the clicked header */ getMenuFilter : function () { var view = this.grid.getView(); if (!view || view.hdCtxIndex === undefined) { return null; } return this.filters.get( view.cm.config[view.hdCtxIndex].dataIndex ); }, /** * @private * Handler called by the grid's hmenu beforeshow event */ onMenu : function (filterMenu) { var filter = this.getMenuFilter(); if (filter) { /* TODO: lazy rendering if (!filter.menu) { filter.menu = filter.createMenu(); } */ this.menu.menu = filter.menu; this.menu.setChecked(filter.active, false); // disable the menu if filter.disabled explicitly set to true this.menu.setDisabled(filter.disabled === true); } this.menu.setVisible(filter !== undefined); this.sep.setVisible(filter !== undefined); }, /** @private */ onCheckChange : function (item, value) { this.getMenuFilter().setActive(value); }, /** @private */ onBeforeCheck : function (check, value) { return !value || this.getMenuFilter().isActivatable(); }, /** * @private * Handler for all events on filters. * @param {String} event Event name * @param {Object} filter Standard signature of the event before the event is fired */ onStateChange : function (event, filter) { if (event === 'serialize') { return; } if (filter == this.getMenuFilter()) { this.menu.setChecked(filter.active, false); } if ((this.autoReload || this.local) && !this.applyingState) { this.deferredUpdate.delay(this.updateBuffer); } this.updateColumnHeadings(); if (!this.applyingState) { this.grid.saveState(); } this.grid.fireEvent('filterupdate', this, filter); }, /** * @private * Handler for store's beforeload event when configured for remote filtering * @param {Object} store * @param {Object} options */ onBeforeLoad : function (store, options) { options.params = options.params || {}; this.cleanParams(options.params); var params = this.buildQuery(this.getFilterData()); Ext.apply(options.params, params); }, /** * @private * Handler for store's load event when configured for local filtering * @param {Object} store * @param {Object} options */ onLoad : function (store, options) { store.filterBy(this.getRecordFilter()); }, /** * @private * Handler called when the grid's view is refreshed */ onRefresh : function () { this.updateColumnHeadings(); },
    /** * Update the styles for the header row based on the active filters */ updateColumnHeadings : function () { var view = this.grid.getView(), i, len, filter; if (view.mainHd) { for (i = 0, len = view.cm.config.length; i < len; i++) { filter = this.getFilter(view.cm.config[i].dataIndex); Ext.fly(view.getHeaderCell(i))[filter && filter.active ? 'addClass' : 'removeClass'](this.filterCls); } } }, /** @private */ reload : function () { if (this.local) { this.grid.store.clearFilter(true); this.grid.store.filterBy(this.getRecordFilter()); } else { var start, store = this.grid.store; this.deferredUpdate.cancel(); if (this.toolbar) { start = store.paramNames.start; if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) { store.lastOptions.params[start] = 0; } } store.reload(); } }, /** * Method factory that generates a record validator for the filters active at the time * of invokation. * @private */ getRecordFilter : function () { var f = [], len, i; this.filters.each(function (filter) { if (filter.active) { f.push(filter); } }); len = f.length; return function (record) { for (i = 0; i < len; i++) { if (!f[i].validateRecord(record)) { return false; } } return true; }; },
    /** * Adds a filter to the collection and observes it for state change. * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object. * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object. */ addFilter : function (config) { var Cls = this.getFilterClass(config.type), filter = config.menu ? config : (new Cls(config)); this.filters.add(filter); Ext.util.Observable.capture(filter, this.onStateChange, this); return filter; },
    /** * Adds filters to the collection. * @param {Array/Ext.grid.ColumnModel} filters Either an Array of * filter configuration objects or an Ext.grid.ColumnModel. The columns * of a passed Ext.grid.ColumnModel will be examined for a filter * property and, if present, will be used as the filter configuration object. */ addFilters : function (filters) { if (filters) { var i, len, filter, cm = false, dI; if (filters instanceof Ext.grid.ColumnModel) { filters = filters.config; cm = true; } for (i = 0, len = filters.length; i < len; i++) { filter = false; if (cm) { dI = filters[i].dataIndex; filter = filters[i].filter || filters[i].filterable; if (filter){ filter = (filter === true) ? {} : filter; Ext.apply(filter, {dataIndex:dI}); // filter type is specified in order of preference: // filter type specified in config // type specified in store's field's type config filter.type = filter.type || this.store.fields.get(dI).type.type; } } else { filter = filters[i]; } // if filter config found add filter for the column if (filter) { this.addFilter(filter); } } } },
    /** * Returns a filter for the given dataIndex, if one exists. * @param {String} dataIndex The dataIndex of the desired filter object. * @return {Ext.ux.grid.filter.Filter} */ getFilter : function (dataIndex) { return this.filters.get(dataIndex); },
    /** * Turns all filters off. This does not clear the configuration information * (see {@link #removeAll}). */ clearFilters : function () { this.filters.each(function (filter) { filter.setActive(false); }); },
    /** * Returns an Array of the currently active filters. * @return {Array} filters Array of the currently active filters. */ getFilterData : function () { var filters = [], i, len; this.filters.each(function (f) { if (f.active) { var d = [].concat(f.serialize()); for (i = 0, len = d.length; i < len; i++) { filters.push({ field: f.dataIndex, data: d[i] }); } } }); return filters; },
    /** * Function to take the active filters data and build it into a query. * The format of the query depends on the {@link #encode} * configuration: *
      * *
    • false : Default *
      * Flatten into query string of the form (assuming {@link #paramPrefix}='filters': *
      
      filters[0][field]="someDataIndex"&
      filters[0][data][comparison]="someValue1"&
      filters[0][data][type]="someValue2"&
      filters[0][data][value]="someValue3"&
           * 
      *
    • *
    • true : *
      * JSON encode the filter data *
      
      filters[0][field]="someDataIndex"&
      filters[0][data][comparison]="someValue1"&
      filters[0][data][type]="someValue2"&
      filters[0][data][value]="someValue3"&
           * 
      *
    • *
    * Override this method to customize the format of the filter query for remote requests. * @param {Array} filters A collection of objects representing active filters and their configuration. * Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured * to be unique as any one filter may be a composite of more basic filters for the same dataIndex. * @return {Object} Query keys and values */ buildQuery : function (filters) { var p = {}, i, f, root, dataPrefix, key, tmp, len = filters.length; if (!this.encode){ for (i = 0; i < len; i++) { f = filters[i]; root = [this.paramPrefix, '[', i, ']'].join(''); p[root + '[field]'] = f.field; dataPrefix = root + '[data]'; for (key in f.data) { p[[dataPrefix, '[', key, ']'].join('')] = f.data[key]; } } } else { tmp = []; for (i = 0; i < len; i++) { f = filters[i]; tmp.push(Ext.apply( {}, {field: f.field}, f.data )); } // only build if there is active filter if (tmp.length > 0){ p[this.paramPrefix] = Ext.util.JSON.encode(tmp); } } return p; },
    /** * Removes filter related query parameters from the provided object. * @param {Object} p Query parameters that may contain filter related fields. */ cleanParams : function (p) { // if encoding just delete the property if (this.encode) { delete p[this.paramPrefix]; // otherwise scrub the object of filter data } else { var regex, key; regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]'); for (key in p) { if (regex.test(key)) { delete p[key]; } } } },
    /** * Function for locating filter classes, overwrite this with your favorite * loader to provide dynamic filter loading. * @param {String} type The type of filter to load ('Filter' is automatically * appended to the passed type; eg, 'string' becomes 'StringFilter'). * @return {Class} The Ext.ux.grid.filter.Class */ getFilterClass : function (type) { // map the supported Ext.data.Field type values into a supported filter switch(type) { case 'auto': type = 'string'; break; case 'int': case 'float': type = 'numeric'; break; case 'bool': type = 'boolean'; break; } return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter']; } }); // register ptype Ext.preg('gridfilters', Ext.ux.grid.GridFilters); Ext.namespace('Ext.ux.grid.filter'); /** * @class Ext.ux.grid.filter.Filter * @extends Ext.util.Observable * Abstract base class for filter implementations. */ Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, {
    /** * @cfg {Boolean} active * Indicates the initial status of the filter (defaults to false). */ active : false,
    /** * True if this filter is active. Use setActive() to alter after configuration. * @type Boolean * @property active */
    /** * @cfg {String} dataIndex * The {@link Ext.data.Store} dataIndex of the field this filter represents. * The dataIndex does not actually have to exist in the store. */ dataIndex : null,
    /** * The filter configuration menu that will be installed into the filter submenu of a column menu. * @type Ext.menu.Menu * @property */ menu : null,
    /** * @cfg {Number} updateBuffer * Number of milliseconds to wait after user interaction to fire an update. Only supported * by filters: 'list', 'numeric', and 'string'. Defaults to 500. */ updateBuffer : 500, constructor : function (config) { Ext.apply(this, config); this.addEvents(
    /** * @event activate * Fires when an inactive filter becomes active * @param {Ext.ux.grid.filter.Filter} this */ 'activate',
    /** * @event deactivate * Fires when an active filter becomes inactive * @param {Ext.ux.grid.filter.Filter} this */ 'deactivate',
    /** * @event serialize * Fires after the serialization process. Use this to attach additional parameters to serialization * data before it is encoded and sent to the server. * @param {Array/Object} data A map or collection of maps representing the current filter configuration. * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized. */ 'serialize',
    /** * @event update * Fires when a filter configuration has changed * @param {Ext.ux.grid.filter.Filter} this The filter object. */ 'update' ); Ext.ux.grid.filter.Filter.superclass.constructor.call(this); this.menu = new Ext.menu.Menu(); this.init(config); if(config && config.value){ this.setValue(config.value); this.setActive(config.active !== false, true); delete config.value; } },
    /** * Destroys this filter by purging any event listeners, and removing any menus. */ destroy : function(){ if (this.menu){ this.menu.destroy(); } this.purgeListeners(); },
    /** * Template method to be implemented by all subclasses that is to * initialize the filter and install required menu items. * Defaults to Ext.emptyFn. */ init : Ext.emptyFn,
    /** * Template method to be implemented by all subclasses that is to * get and return the value of the filter. * Defaults to Ext.emptyFn. * @return {Object} The 'serialized' form of this filter * @methodOf Ext.ux.grid.filter.Filter */ getValue : Ext.emptyFn,
    /** * Template method to be implemented by all subclasses that is to * set the value of the filter and fire the 'update' event. * Defaults to Ext.emptyFn. * @param {Object} data The value to set the filter * @methodOf Ext.ux.grid.filter.Filter */ setValue : Ext.emptyFn,
    /** * Template method to be implemented by all subclasses that is to * return true if the filter has enough configuration information to be activated. * Defaults to return true. * @return {Boolean} */ isActivatable : function(){ return true; },
    /** * Template method to be implemented by all subclasses that is to * get and return serialized filter data for transmission to the server. * Defaults to Ext.emptyFn. */ getSerialArgs : Ext.emptyFn,
    /** * Template method to be implemented by all subclasses that is to * validates the provided Ext.data.Record against the filters configuration. * Defaults to return true. * @param {Ext.data.Record} record The record to validate * @return {Boolean} true if the record is valid within the bounds * of the filter, false otherwise. */ validateRecord : function(){ return true; },
    /** * Returns the serialized filter data for transmission to the server * and fires the 'serialize' event. * @return {Object/Array} An object or collection of objects containing * key value pairs representing the current configuration of the filter. * @methodOf Ext.ux.grid.filter.Filter */ serialize : function(){ var args = this.getSerialArgs(); this.fireEvent('serialize', args, this); return args; }, /** @private */ fireUpdate : function(){ if (this.active) { this.fireEvent('update', this); } this.setActive(this.isActivatable()); },
    /** * Sets the status of the filter and fires the appropriate events. * @param {Boolean} active The new filter state. * @param {Boolean} suppressEvent True to prevent events from being fired. * @methodOf Ext.ux.grid.filter.Filter */ setActive : function(active, suppressEvent){ if(this.active != active){ this.active = active; if (suppressEvent !== true) { this.fireEvent(active ? 'activate' : 'deactivate', this); } } } });/** * @class Ext.ux.grid.filter.BooleanFilter * @extends Ext.ux.grid.filter.Filter * Boolean filters use unique radio group IDs (so you can have more than one!) *

    Example Usage:

    *
        
    var filters = new Ext.ux.grid.GridFilters({
        ...
        filters: [{
            // required configs
            type: 'boolean',
            dataIndex: 'visible'
    
            // optional configs
            defaultValue: null, // leave unselected (false selected by default)
            yesText: 'Yes',     // default
            noText: 'No'        // default
        }]
    });
     * 
    */ Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
    /** * @cfg {Boolean} defaultValue * Set this to null if you do not want either option to be checked by default. Defaults to false. */ defaultValue : false,
    /** * @cfg {String} yesText * Defaults to 'Yes'. */ yesText : 'Yes',
    /** * @cfg {String} noText * Defaults to 'No'. */ noText : 'No', /** * @private * Template method that is to initialize the filter and install required menu items. */ init : function (config) { var gId = Ext.id(); this.options = [ new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}), new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})]; this.menu.add(this.options[0], this.options[1]); for(var i=0; i/** * Template method that is to validate the provided Ext.data.Record * against the filters configuration. * @param {Ext.data.Record} record The record to validate * @return {Boolean} true if the record is valid within the bounds * of the filter, false otherwise. */ validateRecord : function (record) { return record.get(this.dataIndex) == this.getValue(); } });/** * @class Ext.ux.grid.filter.DateFilter * @extends Ext.ux.grid.filter.Filter * Filter by a configurable Ext.menu.DateMenu *

    Example Usage:

    *
        
    var filters = new Ext.ux.grid.GridFilters({
        ...
        filters: [{
            // required configs
            type: 'date',
            dataIndex: 'dateAdded',
            
            // optional configs
            dateFormat: 'm/d/Y',  // default
            beforeText: 'Before', // default
            afterText: 'After',   // default
            onText: 'On',         // default
            pickerOpts: {
                // any DateMenu configs
            },
    
            active: true // default is false
        }]
    });
     * 
    */ Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
    /** * @cfg {String} afterText * Defaults to 'After'. */ afterText : 'After',
    /** * @cfg {String} beforeText * Defaults to 'Before'. */ beforeText : 'Before',
    /** * @cfg {Object} compareMap * Map for assigning the comparison values used in serialization. */ compareMap : { before: 'lt', after: 'gt', on: 'eq' },
    /** * @cfg {String} dateFormat * The date format to return when using getValue. * Defaults to 'm/d/Y'. */ dateFormat : 'm/d/Y',
    /** * @cfg {Date} maxDate * Allowable date as passed to the Ext.DatePicker * Defaults to undefined. */
    /** * @cfg {Date} minDate * Allowable date as passed to the Ext.DatePicker * Defaults to undefined. */
    /** * @cfg {Array} menuItems * The items to be shown in this menu * Defaults to:
         * menuItems : ['before', 'after', '-', 'on'],
         * 
    */ menuItems : ['before', 'after', '-', 'on'],
    /** * @cfg {Object} menuItemCfgs * Default configuration options for each menu item */ menuItemCfgs : { selectOnFocus: true, width: 125 },
    /** * @cfg {String} onText * Defaults to 'On'. */ onText : 'On',
    /** * @cfg {Object} pickerOpts * Configuration options for the date picker associated with each field. */ pickerOpts : {}, /** * @private * Template method that is to initialize the filter and install required menu items. */ init : function (config) { var menuCfg, i, len, item, cfg, Cls; menuCfg = Ext.apply(this.pickerOpts, { minDate: this.minDate, maxDate: this.maxDate, format: this.dateFormat, listeners: { scope: this, select: this.onMenuSelect } }); this.fields = {}; for (i = 0, len = this.menuItems.length; i < len; i++) { item = this.menuItems[i]; if (item !== '-') { cfg = { itemId: 'range-' + item, text: this[item + 'Text'], menu: new Ext.menu.DateMenu( Ext.apply(menuCfg, { itemId: item }) ), listeners: { scope: this, checkchange: this.onCheckChange } }; Cls = Ext.menu.CheckItem; item = this.fields[item] = new Cls(cfg); } //this.add(item); this.menu.add(item); } }, onCheckChange : function () { this.setActive(this.isActivatable()); this.fireEvent('update', this); }, /** * @private * Handler method called when there is a keyup event on an input * item of this menu. */ onInputKeyUp : function (field, e) { var k = e.getKey(); if (k == e.RETURN && field.isValid()) { e.stopEvent(); this.menu.hide(true); return; } },
    /** * Handler for when the menu for a field fires the 'select' event * @param {Object} date * @param {Object} menuItem * @param {Object} value * @param {Object} picker */ onMenuSelect : function (menuItem, value, picker) { var fields = this.fields, field = this.fields[menuItem.itemId]; field.setChecked(true); if (field == fields.on) { fields.before.setChecked(false, true); fields.after.setChecked(false, true); } else { fields.on.setChecked(false, true); if (field == fields.after && fields.before.menu.picker.value < value) { fields.before.setChecked(false, true); } else if (field == fields.before && fields.after.menu.picker.value > value) { fields.after.setChecked(false, true); } } this.fireEvent('update', this); }, /** * @private * Template method that is to get and return the value of the filter. * @return {String} The value of this filter */ getValue : function () { var key, result = {}; for (key in this.fields) { if (this.fields[key].checked) { result[key] = this.fields[key].menu.picker.getValue(); } } return result; }, /** * @private * Template method that is to set the value of the filter. * @param {Object} value The value to set the filter * @param {Boolean} preserve true to preserve the checked status * of the other fields. Defaults to false, unchecking the * other fields */ setValue : function (value, preserve) { var key; for (key in this.fields) { if(value[key]){ this.fields[key].menu.picker.setValue(value[key]); this.fields[key].setChecked(true); } else if (!preserve) { this.fields[key].setChecked(false); } } this.fireEvent('update', this); }, /** * @private * Template method that is to return true if the filter * has enough configuration information to be activated. * @return {Boolean} */ isActivatable : function () { var key; for (key in this.fields) { if (this.fields[key].checked) { return true; } } return false; }, /** * @private * Template method that is to get and return serialized filter data for * transmission to the server. * @return {Object/Array} An object or collection of objects containing * key value pairs representing the current configuration of the filter. */ getSerialArgs : function () { var args = []; for (var key in this.fields) { if(this.fields[key].checked){ args.push({ type: 'date', comparison: this.compareMap[key], value: this.getFieldValue(key).format(this.dateFormat) }); } } return args; },
    /** * Get and return the date menu picker value * @param {String} item The field identifier ('before', 'after', 'on') * @return {Date} Gets the current selected value of the date field */ getFieldValue : function(item){ return this.fields[item].menu.picker.getValue(); },
    /** * Gets the menu picker associated with the passed field * @param {String} item The field identifier ('before', 'after', 'on') * @return {Object} The menu picker */ getPicker : function(item){ return this.fields[item].menu.picker; },
    /** * Template method that is to validate the provided Ext.data.Record * against the filters configuration. * @param {Ext.data.Record} record The record to validate * @return {Boolean} true if the record is valid within the bounds * of the filter, false otherwise. */ validateRecord : function (record) { var key, pickerValue, val = record.get(this.dataIndex); if(!Ext.isDate(val)){ return false; } val = val.clearTime(true).getTime(); for (key in this.fields) { if (this.fields[key].checked) { pickerValue = this.getFieldValue(key).clearTime(true).getTime(); if (key == 'before' && pickerValue <= val) { return false; } if (key == 'after' && pickerValue >= val) { return false; } if (key == 'on' && pickerValue != val) { return false; } } } return true; } });/** * @class Ext.ux.grid.filter.ListFilter * @extends Ext.ux.grid.filter.Filter *

    List filters are able to be preloaded/backed by an Ext.data.Store to load * their options the first time they are shown. ListFilter utilizes the * {@link Ext.ux.menu.ListMenu} component.

    *

    Although not shown here, this class accepts all configuration options * for {@link Ext.ux.menu.ListMenu}.

    * *

    Example Usage:

    *
        
    var filters = new Ext.ux.grid.GridFilters({
        ...
        filters: [{
            type: 'list',
            dataIndex: 'size',
            phpMode: true,
            // options will be used as data to implicitly creates an ArrayStore
            options: ['extra small', 'small', 'medium', 'large', 'extra large']
        }]
    });
     * 
    * */ Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
    /** * @cfg {Array} options *

    data to be used to implicitly create a data store * to back this list when the data source is local. If the * data for the list is remote, use the {@link #store} * config instead.

    *

    Each item within the provided array may be in one of the * following formats:

    *
      *
    • Array : *
      
      options: [
          [11, 'extra small'], 
          [18, 'small'],
          [22, 'medium'],
          [35, 'large'],
          [44, 'extra large']
      ]
           * 
      *
    • *
    • Object : *
      
      labelField: 'name', // override default of 'text'
      options: [
          {id: 11, name:'extra small'}, 
          {id: 18, name:'small'}, 
          {id: 22, name:'medium'}, 
          {id: 35, name:'large'}, 
          {id: 44, name:'extra large'} 
      ]
           * 
      *
    • *
    • String : *
      
           * options: ['extra small', 'small', 'medium', 'large', 'extra large']
           * 
      *
    • */
      /** * @cfg {Boolean} phpMode *

      Adjust the format of this filter. Defaults to false.

      *

      When GridFilters @cfg encode = false (default):

      *
      
      // phpMode == false (default):
      filter[0][data][type] list
      filter[0][data][value] value1
      filter[0][data][value] value2
      filter[0][field] prod 
      
      // phpMode == true:
      filter[0][data][type] list
      filter[0][data][value] value1, value2
      filter[0][field] prod 
           * 
      * When GridFilters @cfg encode = true: *
      
      // phpMode == false (default):
      filter : [{"type":"list","value":["small","medium"],"field":"size"}]
      
      // phpMode == true:
      filter : [{"type":"list","value":"small,medium","field":"size"}]
           * 
      */ phpMode : false,
      /** * @cfg {Ext.data.Store} store * The {@link Ext.data.Store} this list should use as its data source * when the data source is remote. If the data for the list * is local, use the {@link #options} config instead. */ /** * @private * Template method that is to initialize the filter and install required menu items. * @param {Object} config */ init : function (config) { this.dt = new Ext.util.DelayedTask(this.fireUpdate, this); // if a menu already existed, do clean up first if (this.menu){ this.menu.destroy(); } this.menu = new Ext.ux.menu.ListMenu(config); this.menu.on('checkchange', this.onCheckChange, this); }, /** * @private * Template method that is to get and return the value of the filter. * @return {String} The value of this filter */ getValue : function () { return this.menu.getSelected(); }, /** * @private * Template method that is to set the value of the filter. * @param {Object} value The value to set the filter */ setValue : function (value) { this.menu.setSelected(value); this.fireEvent('update', this); }, /** * @private * Template method that is to return true if the filter * has enough configuration information to be activated. * @return {Boolean} */ isActivatable : function () { return this.getValue().length > 0; }, /** * @private * Template method that is to get and return serialized filter data for * transmission to the server. * @return {Object/Array} An object or collection of objects containing * key value pairs representing the current configuration of the filter. */ getSerialArgs : function () { var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()}; return args; }, /** @private */ onCheckChange : function(){ this.dt.delay(this.updateBuffer); },
      /** * Template method that is to validate the provided Ext.data.Record * against the filters configuration. * @param {Ext.data.Record} record The record to validate * @return {Boolean} true if the record is valid within the bounds * of the filter, false otherwise. */ validateRecord : function (record) { return this.getValue().indexOf(record.get(this.dataIndex)) > -1; } });/** * @class Ext.ux.grid.filter.NumericFilter * @extends Ext.ux.grid.filter.Filter * Filters using an Ext.ux.menu.RangeMenu. *

      Example Usage:

      *
          
      var filters = new Ext.ux.grid.GridFilters({
          ...
          filters: [{
              type: 'numeric',
              dataIndex: 'price'
          }]
      });
       * 
      */ Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
      /** * @cfg {Object} fieldCls * The Class to use to construct each field item within this menu * Defaults to:
           * fieldCls : Ext.form.NumberField
           * 
      */ fieldCls : Ext.form.NumberField,
      /** * @cfg {Object} fieldCfg * The default configuration options for any field item unless superseded * by the {@link #fields} configuration. * Defaults to:
           * fieldCfg : {}
           * 
      * Example usage: *
      
      fieldCfg : {
          width: 150,
      },
           * 
      */
      /** * @cfg {Object} fields * The field items may be configured individually * Defaults to undefined. * Example usage: *
      
      fields : {
          gt: { // override fieldCfg options
              width: 200,
              fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}
          }
      },
           * 
      */
      /** * @cfg {Object} iconCls * The iconCls to be applied to each comparator field item. * Defaults to:
      iconCls : {
          gt : 'ux-rangemenu-gt',
          lt : 'ux-rangemenu-lt',
          eq : 'ux-rangemenu-eq'
      }
           * 
      */ iconCls : { gt : 'ux-rangemenu-gt', lt : 'ux-rangemenu-lt', eq : 'ux-rangemenu-eq' },
      /** * @cfg {Object} menuItemCfgs * Default configuration options for each menu item * Defaults to:
      menuItemCfgs : {
          emptyText: 'Enter Filter Text...',
          selectOnFocus: true,
          width: 125
      }
           * 
      */ menuItemCfgs : { emptyText: 'Enter Filter Text...', selectOnFocus: true, width: 125 },
      /** * @cfg {Array} menuItems * The items to be shown in this menu. Items are added to the menu * according to their position within this array. Defaults to:
           * menuItems : ['lt','gt','-','eq']
           * 
      */ menuItems : ['lt', 'gt', '-', 'eq'], /** * @private * Template method that is to initialize the filter and install required menu items. */ init : function (config) { // if a menu already existed, do clean up first if (this.menu){ this.menu.destroy(); } this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, { // pass along filter configs to the menu fieldCfg : this.fieldCfg || {}, fieldCls : this.fieldCls, fields : this.fields || {}, iconCls: this.iconCls, menuItemCfgs: this.menuItemCfgs, menuItems: this.menuItems, updateBuffer: this.updateBuffer })); // relay the event fired by the menu this.menu.on('update', this.fireUpdate, this); }, /** * @private * Template method that is to get and return the value of the filter. * @return {String} The value of this filter */ getValue : function () { return this.menu.getValue(); }, /** * @private * Template method that is to set the value of the filter. * @param {Object} value The value to set the filter */ setValue : function (value) { this.menu.setValue(value); }, /** * @private * Template method that is to return true if the filter * has enough configuration information to be activated. * @return {Boolean} */ isActivatable : function () { var values = this.getValue(); for (key in values) { if (values[key] !== undefined) { return true; } } return false; }, /** * @private * Template method that is to get and return serialized filter data for * transmission to the server. * @return {Object/Array} An object or collection of objects containing * key value pairs representing the current configuration of the filter. */ getSerialArgs : function () { var key, args = [], values = this.menu.getValue(); for (key in values) { args.push({ type: 'numeric', comparison: key, value: values[key] }); } return args; },
      /** * Template method that is to validate the provided Ext.data.Record * against the filters configuration. * @param {Ext.data.Record} record The record to validate * @return {Boolean} true if the record is valid within the bounds * of the filter, false otherwise. */ validateRecord : function (record) { var val = record.get(this.dataIndex), values = this.getValue(); if (values.eq !== undefined && val != values.eq) { return false; } if (values.lt !== undefined && val >= values.lt) { return false; } if (values.gt !== undefined && val <= values.gt) { return false; } return true; } });/** * @class Ext.ux.grid.filter.StringFilter * @extends Ext.ux.grid.filter.Filter * Filter by a configurable Ext.form.TextField *

      Example Usage:

      *
          
      var filters = new Ext.ux.grid.GridFilters({
          ...
          filters: [{
              // required configs
              type: 'string',
              dataIndex: 'name',
              
              // optional configs
              value: 'foo',
              active: true, // default is false
              iconCls: 'ux-gridfilter-text-icon' // default
              // any Ext.form.TextField configs accepted
          }]
      });
       * 
      */ Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
      /** * @cfg {String} iconCls * The iconCls to be applied to the menu item. * Defaults to 'ux-gridfilter-text-icon'. */ iconCls : 'ux-gridfilter-text-icon', emptyText: 'Enter Filter Text...', selectOnFocus: true, width: 125, /** * @private * Template method that is to initialize the filter and install required menu items. */ init : function (config) { Ext.applyIf(config, { enableKeyEvents: true, iconCls: this.iconCls, listeners: { scope: this, keyup: this.onInputKeyUp } }); this.inputItem = new Ext.form.TextField(config); this.menu.add(this.inputItem); this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this); }, /** * @private * Template method that is to get and return the value of the filter. * @return {String} The value of this filter */ getValue : function () { return this.inputItem.getValue(); }, /** * @private * Template method that is to set the value of the filter. * @param {Object} value The value to set the filter */ setValue : function (value) { this.inputItem.setValue(value); this.fireEvent('update', this); }, /** * @private * Template method that is to return true if the filter * has enough configuration information to be activated. * @return {Boolean} */ isActivatable : function () { return this.inputItem.getValue().length > 0; }, /** * @private * Template method that is to get and return serialized filter data for * transmission to the server. * @return {Object/Array} An object or collection of objects containing * key value pairs representing the current configuration of the filter. */ getSerialArgs : function () { return {type: 'string', value: this.getValue()}; },
      /** * Template method that is to validate the provided Ext.data.Record * against the filters configuration. * @param {Ext.data.Record} record The record to validate * @return {Boolean} true if the record is valid within the bounds * of the filter, false otherwise. */ validateRecord : function (record) { var val = record.get(this.dataIndex); if(typeof val != 'string') { return (this.getValue().length === 0); } return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1; }, /** * @private * Handler method called when there is a keyup event on this.inputItem */ onInputKeyUp : function (field, e) { var k = e.getKey(); if (k == e.RETURN && field.isValid()) { e.stopEvent(); this.menu.hide(true); return; } // restart the timer this.updateTask.delay(this.updateBuffer); } }); Ext.namespace('Ext.ux.menu'); /** * @class Ext.ux.menu.ListMenu * @extends Ext.menu.Menu * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}. * Although not listed as configuration options for this class, this class * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}. */ Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {
      /** * @cfg {String} labelField * Defaults to 'text'. */ labelField : 'text',
      /** * @cfg {String} paramPrefix * Defaults to 'Loading...'. */ loadingText : 'Loading...',
      /** * @cfg {Boolean} loadOnShow * Defaults to true. */ loadOnShow : true,
      /** * @cfg {Boolean} single * Specify true to group all items in this list into a single-select * radio button group. Defaults to false. */ single : false, constructor : function (cfg) { this.selected = []; this.addEvents(
      /** * @event checkchange * Fires when there is a change in checked items from this list * @param {Object} item Ext.menu.CheckItem * @param {Object} checked The checked value that was set */ 'checkchange' ); Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {}); if(!cfg.store && cfg.options){ var options = []; for(var i=0, len=cfg.options.length; i
    /** * Lists will initially show a 'loading' item while the data is retrieved from the store. * In some cases the loaded data will result in a list that goes off the screen to the * right (as placement calculations were done with the loading item). This adapter will * allow show to be called with no arguments to show with the previous arguments and * thus recalculate the width and potentially hang the menu from the left. */ show : function () { var lastArgs = null; return function(){ if(arguments.length === 0){ Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs); } else { lastArgs = arguments; if (this.loadOnShow && !this.loaded) { this.store.load(); } Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments); } }; }(), /** @private */ onLoad : function (store, records) { var visible = this.isVisible(); this.hide(false); this.removeAll(true); var gid = this.single ? Ext.id() : null; for(var i=0, len=records.length; i -1, hideOnClick: false}); item.itemId = records[i].id; item.on('checkchange', this.checkChange, this); this.add(item); } this.loaded = true; if (visible) { this.show(); } this.fireEvent('load', this, records); },
    /** * Get the selected items. * @return {Array} selected */ getSelected : function () { return this.selected; }, /** @private */ setSelected : function (value) { value = this.selected = [].concat(value); if (this.loaded) { this.items.each(function(item){ item.setChecked(false, true); for (var i = 0, len = value.length; i < len; i++) { if (item.itemId == value[i]) { item.setChecked(true, true); } } }, this); } },
    /** * Handler for the 'checkchange' event from an check item in this menu * @param {Object} item Ext.menu.CheckItem * @param {Object} checked The checked value that was set */ checkChange : function (item, checked) { var value = []; this.items.each(function(item){ if (item.checked) { value.push(item.itemId); } },this); this.selected = value; this.fireEvent('checkchange', item, checked); } });Ext.ns('Ext.ux.menu'); /** * @class Ext.ux.menu.RangeMenu * @extends Ext.menu.Menu * Custom implementation of Ext.menu.Menu that has preconfigured * items for gt, lt, eq. *

    Example Usage:

    *
        
    
     * 
    */ Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, { constructor : function (config) { Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config); this.addEvents(
    /** * @event update * Fires when a filter configuration has changed * @param {Ext.ux.grid.filter.Filter} this The filter object. */ 'update' ); this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this); var i, len, item, cfg, Cls; for (i = 0, len = this.menuItems.length; i < len; i++) { item = this.menuItems[i]; if (item !== '-') { // defaults cfg = { itemId: 'range-' + item, enableKeyEvents: true, iconCls: this.iconCls[item] || 'no-icon', listeners: { scope: this, keyup: this.onInputKeyUp } }; Ext.apply( cfg, // custom configs Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]), // configurable defaults this.menuItemCfgs ); Cls = cfg.fieldCls || this.fieldCls; item = this.fields[item] = new Cls(cfg); } this.add(item); } }, /** * @private * called by this.updateTask */ fireUpdate : function () { this.fireEvent('update', this); },
    /** * Get and return the value of the filter. * @return {String} The value of this filter */ getValue : function () { var result = {}, key, field; for (key in this.fields) { field = this.fields[key]; if (field.isValid() && String(field.getValue()).length > 0) { result[key] = field.getValue(); } } return result; },
    /** * Set the value of this menu and fires the 'update' event. * @param {Object} data The data to assign to this menu */ setValue : function (data) { var key; for (key in this.fields) { this.fields[key].setValue(data[key] !== undefined ? data[key] : ''); } this.fireEvent('update', this); }, /** * @private * Handler method called when there is a keyup event on an input * item of this menu. */ onInputKeyUp : function (field, e) { var k = e.getKey(); if (k == e.RETURN && field.isValid()) { e.stopEvent(); this.hide(true); return; } if (field == this.fields.eq) { if (this.fields.gt) { this.fields.gt.setValue(null); } if (this.fields.lt) { this.fields.lt.setValue(null); } } else { this.fields.eq.setValue(null); } // restart the timer this.updateTask.delay(this.updateBuffer); } }); 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; var v = this.view = grid.getView(); 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.grid.getColumnModel().config, 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.grid.getColumnModel().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){ if(!this.isGrouped()){ return; } var gs = this.view.getGroups(), len = gs.length, i = 0, s; for(; 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){ if(!this.isGrouped()){ return; } var gs = this.view.getGroups(), len = gs.length, i = 0, j, s, cells, wlen = ws.length; for(; i < len; i++){ s = gs[i].childNodes[2]; s.style.width = tw; s.firstChild.style.width = tw; cells = s.firstChild.rows[0].childNodes; for(j = 0; j < wlen; j++){ cells[j].style.width = ws[j]; } } }, doHidden : function(col, hidden, tw){ if(!this.isGrouped()){ return; } var gs = this.view.getGroups(), len = gs.length, i = 0, s, display = hidden ? 'none' : ''; for(; 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; } }, isGrouped : function(){ return !Ext.isEmpty(this.grid.getStore().groupField); }, // 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 = Ext.getDom(gid); if(!g){ return false; } var rs = []; this.grid.getStore().each(function(r){ if(r._groupId == gid){ rs[rs.length] = r; } }); var cs = this.view.getColumnData(), data = this.calculate(rs, cs), markup = this.renderSummary({data: data}, cs), 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), node = this.getSummaryNode(gid); if(node){ node.innerHTML = '
    ' + msg + '
    '; } } }); //backwards compat Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;
    /** * Calculation types for summary row:

      *
    • sum :
    • *
    • count :
    • *
    • max :
    • *
    • min :
    • *
    • average :
    • *
    *

    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(), gvalue = rs[0].data[gcol], 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.getStore().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 reader = this.grid.getStore().reader, json = reader.jsonData, fields = reader.recordType.prototype.fields, v; if(json && json.summaryData){ v = json.summaryData[groupValue]; if(v){ return reader.extractValues(v, fields.items, fields.length); } } 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){ return false; } if(!this.rendered){ this.activeTab = item; return true; } if(this.activeTab != item && this.fireEvent('beforetabchange', this, item, this.activeTab) !== false){ if(this.activeTab && this.activeTab != this.mainItem){ var oldEl = this.getTabEl(this.activeTab); if(oldEl){ Ext.fly(oldEl).removeClass('x-grouptabs-strip-active'); } } 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); } this.fireEvent('tabchange', this, item); return true; } return false; }, 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); }); this.items.each(function(item){ item.on('tabchange',function(item){ this.fireEvent('tabchange', this, item.activeTab); }, this); },this); }, 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.groupTpl) { var tt = new Ext.Template( '
  • ', '', '', '{text}', '
  • ' ); tt.disableFormats = true; tt.compile(); Ext.ux.GroupTabPanel.prototype.groupTpl = 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, 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'); this.syncTabJoint(); }, toggleGroup: function(groupEl){ if(groupEl.isXType) { groupEl = this.getGroupEl(groupEl); } Ext.fly(groupEl).toggleClass('x-grouptabs-expanded'); this.syncTabJoint(); }, collapseGroup: function(groupEl){ if(groupEl.isXType) { groupEl = this.getGroupEl(groupEl); } Ext.fly(groupEl).removeClass('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], 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.groupTpl.insertBefore(before, p) : this.groupTpl.append(this.strip, p), tl = this.createCorner(el, 'top-' + this.tabPosition), bl = this.createCorner(el, 'bottom-' + this.tabPosition); group.tabEl = el; 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, { scope: this, changemainitem: this.onGroupChangeMainItem, beforetabchange: this.onGroupBeforeTabChange }); }, setActiveGroup : function(group) { group = this.getComponent(group); if(!group){ return false; } if(!this.rendered){ this.activeGroup = group; return true; } if(this.activeGroup != group && this.fireEvent('beforegroupchange', this, group, this.activeGroup) !== false){ if(this.activeGroup){ this.activeGroup.activeTab = null; var oldEl = this.getGroupEl(this.activeGroup); if(oldEl){ Ext.fly(oldEl).removeClass('x-grouptabs-strip-active'); } } 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); this.fireEvent('groupchange', this, group); return true; } return false; }, 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)); if(group !== this.activeGroup) { return 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; i', '
    ', '
    {lockedHeader}
    ', '
    {lockedBody}
    ', '
    ', '
    ', '
    {header}
    ', '
    {body}
    ', '
    ', '
     
    ', '
     
    ', '' ); } this.templates = ts; Ext.ux.grid.LockingGridView.superclass.initTemplates.call(this); }, getEditorParent : function(ed){ return this.el.dom; }, initElements : function(){ var el = Ext.get(this.grid.getGridEl().dom.firstChild), lockedWrap = el.child('div.x-grid3-locked'), lockedHd = lockedWrap.child('div.x-grid3-header'), lockedScroller = lockedWrap.child('div.x-grid3-scroller'), mainWrap = el.child('div.x-grid3-viewport'), mainHd = mainWrap.child('div.x-grid3-header'), scroller = mainWrap.child('div.x-grid3-scroller'); if (this.grid.hideHeaders) { lockedHd.setDisplayed(false); mainHd.setDisplayed(false); } if(this.forceFit){ scroller.setStyle('overflow-x', 'hidden'); } Ext.apply(this, { el : el, mainWrap: mainWrap, mainHd : mainHd, innerHd : mainHd.dom.firstChild, scroller: scroller, mainBody: scroller.child('div.x-grid3-body'), focusEl : scroller.child('a'), resizeMarker: el.child('div.x-grid3-resize-marker'), resizeProxy : el.child('div.x-grid3-resize-proxy'), lockedWrap: lockedWrap, lockedHd: lockedHd, lockedScroller: lockedScroller, lockedBody: lockedScroller.child('div.x-grid3-body'), lockedInnerHd: lockedHd.child('div.x-grid3-header-inner', true) }); this.focusEl.swallowEvent('click', true); }, getLockedRows : function(){ return this.hasRows() ? this.lockedBody.dom.childNodes : []; }, getLockedRow : function(row){ return this.getLockedRows()[row]; }, getCell : function(row, col){ var lockedLen = this.cm.getLockedCount(); if(col < lockedLen){ return this.getLockedRow(row).getElementsByTagName('td')[col]; } return Ext.ux.grid.LockingGridView.superclass.getCell.call(this, row, col - lockedLen); }, getHeaderCell : function(index){ var lockedLen = this.cm.getLockedCount(); if(index < lockedLen){ return this.lockedHd.dom.getElementsByTagName('td')[index]; } return Ext.ux.grid.LockingGridView.superclass.getHeaderCell.call(this, index - lockedLen); }, addRowClass : function(row, cls){ var lockedRow = this.getLockedRow(row); if(lockedRow){ this.fly(lockedRow).addClass(cls); } Ext.ux.grid.LockingGridView.superclass.addRowClass.call(this, row, cls); }, removeRowClass : function(row, cls){ var lockedRow = this.getLockedRow(row); if(lockedRow){ this.fly(lockedRow).removeClass(cls); } Ext.ux.grid.LockingGridView.superclass.removeRowClass.call(this, row, cls); }, removeRow : function(row) { Ext.removeNode(this.getLockedRow(row)); Ext.ux.grid.LockingGridView.superclass.removeRow.call(this, row); }, removeRows : function(firstRow, lastRow){ var lockedBody = this.lockedBody.dom, rowIndex = firstRow; for(; rowIndex <= lastRow; rowIndex++){ Ext.removeNode(lockedBody.childNodes[firstRow]); } Ext.ux.grid.LockingGridView.superclass.removeRows.call(this, firstRow, lastRow); }, syncScroll : function(e){ this.lockedScroller.dom.scrollTop = this.scroller.dom.scrollTop; Ext.ux.grid.LockingGridView.superclass.syncScroll.call(this, e); }, updateSortIcon : function(col, dir){ var sortClasses = this.sortClasses, lockedHeaders = this.lockedHd.select('td').removeClass(sortClasses), headers = this.mainHd.select('td').removeClass(sortClasses), lockedLen = this.cm.getLockedCount(), cls = sortClasses[dir == 'DESC' ? 1 : 0]; if(col < lockedLen){ lockedHeaders.item(col).addClass(cls); }else{ headers.item(col - lockedLen).addClass(cls); } }, updateAllColumnWidths : function(){ var tw = this.getTotalWidth(), clen = this.cm.getColumnCount(), lw = this.getLockedWidth(), llen = this.cm.getLockedCount(), ws = [], len, i; this.updateLockedWidth(); for(i = 0; i < clen; i++){ ws[i] = this.getColumnWidth(i); var hd = this.getHeaderCell(i); hd.style.width = ws[i]; } var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j; for(i = 0, len = ns.length; i < len; i++){ row = lns[i]; row.style.width = lw; if(row.firstChild){ row.firstChild.style.width = lw; trow = row.firstChild.rows[0]; for (j = 0; j < llen; j++) { trow.childNodes[j].style.width = ws[j]; } } row = ns[i]; row.style.width = tw; if(row.firstChild){ row.firstChild.style.width = tw; trow = row.firstChild.rows[0]; for (j = llen; j < clen; j++) { trow.childNodes[j - llen].style.width = ws[j]; } } } this.onAllColumnWidthsUpdated(ws, tw); this.syncHeaderHeight(); }, updateColumnWidth : function(col, width){ var w = this.getColumnWidth(col), llen = this.cm.getLockedCount(), ns, rw, c, row; this.updateLockedWidth(); if(col < llen){ ns = this.getLockedRows(); rw = this.getLockedWidth(); c = col; }else{ ns = this.getRows(); rw = this.getTotalWidth(); c = col - llen; } var hd = this.getHeaderCell(col); hd.style.width = w; for(var i = 0, len = ns.length; i < len; i++){ row = ns[i]; row.style.width = rw; if(row.firstChild){ row.firstChild.style.width = rw; row.firstChild.rows[0].childNodes[c].style.width = w; } } this.onColumnWidthUpdated(col, w, this.getTotalWidth()); this.syncHeaderHeight(); }, updateColumnHidden : function(col, hidden){ var llen = this.cm.getLockedCount(), ns, rw, c, row, display = hidden ? 'none' : ''; this.updateLockedWidth(); if(col < llen){ ns = this.getLockedRows(); rw = this.getLockedWidth(); c = col; }else{ ns = this.getRows(); rw = this.getTotalWidth(); c = col - llen; } var hd = this.getHeaderCell(col); hd.style.display = display; for(var i = 0, len = ns.length; i < len; i++){ row = ns[i]; row.style.width = rw; if(row.firstChild){ row.firstChild.style.width = rw; row.firstChild.rows[0].childNodes[c].style.display = display; } } this.onColumnHiddenUpdated(col, hidden, this.getTotalWidth()); delete this.lastViewWidth; this.layout(); }, doRender : function(cs, rs, ds, startRow, colCount, stripe){ var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1, tstyle = 'width:'+this.getTotalWidth()+';', lstyle = 'width:'+this.getLockedWidth()+';', buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r; for(var j = 0, len = rs.length; j < len; j++){ r = rs[j]; cb = []; lcb = []; var rowIndex = (j+startRow); 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 ' : '')) + (this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : ''); p.attr = p.cellAttr = ''; p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds); p.style = c.style; if(Ext.isEmpty(p.value)){ p.value = ' '; } if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){ p.css += ' x-grid3-dirty-cell'; } if(c.locked){ lcb[lcb.length] = ct.apply(p); }else{ 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(''); rp.tstyle = tstyle; buf[buf.length] = rt.apply(rp); rp.cells = lcb.join(''); rp.tstyle = lstyle; lbuf[lbuf.length] = rt.apply(rp); } return [buf.join(''), lbuf.join('')]; }, processRows : function(startRow, skipStripe){ if(!this.ds || this.ds.getCount() < 1){ return; } var rows = this.getRows(), lrows = this.getLockedRows(), row, lrow; skipStripe = skipStripe || !this.grid.stripeRows; startRow = startRow || 0; for(var i = 0, len = rows.length; i < len; ++i){ row = rows[i]; lrow = lrows[i]; row.rowIndex = i; lrow.rowIndex = i; if(!skipStripe){ row.className = row.className.replace(this.rowClsRe, ' '); lrow.className = lrow.className.replace(this.rowClsRe, ' '); if ((i + 1) % 2 === 0){ row.className += ' x-grid3-row-alt'; lrow.className += ' x-grid3-row-alt'; } } this.syncRowHeights(row, lrow); } if(startRow === 0){ Ext.fly(rows[0]).addClass(this.firstRowCls); Ext.fly(lrows[0]).addClass(this.firstRowCls); } Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls); Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls); }, syncRowHeights: function(row1, row2){ if(this.syncHeights){ var el1 = Ext.get(row1), el2 = Ext.get(row2), h1 = el1.getHeight(), h2 = el2.getHeight(); if(h1 > h2){ el2.setHeight(h1); }else if(h2 > h1){ el1.setHeight(h2); } } }, afterRender : function(){ if(!this.ds || !this.cm){ return; } var bd = this.renderRows() || [' ', ' ']; this.mainBody.dom.innerHTML = bd[0]; this.lockedBody.dom.innerHTML = bd[1]; this.processRows(0, true); if(this.deferEmptyText !== true){ this.applyEmptyText(); } this.grid.fireEvent('viewready', this.grid); }, renderUI : function(){ var templates = this.templates, header = this.renderHeaders(), body = templates.body.apply({rows:' '}); return templates.masterTpl.apply({ body : body, header: header[0], ostyle: 'width:' + this.getOffsetWidth() + ';', bstyle: 'width:' + this.getTotalWidth() + ';', lockedBody: body, lockedHeader: header[1], lstyle: 'width:'+this.getLockedWidth()+';' }); }, afterRenderUI: function(){ var g = this.grid; this.initElements(); Ext.fly(this.innerHd).on('click', this.handleHdDown, this); Ext.fly(this.lockedInnerHd).on('click', this.handleHdDown, this); this.mainHd.on({ scope: this, mouseover: this.handleHdOver, mouseout: this.handleHdOut, mousemove: this.handleHdMove }); this.lockedHd.on({ scope: this, mouseover: this.handleHdOver, mouseout: this.handleHdOut, mousemove: this.handleHdMove }); this.scroller.on('scroll', this.syncScroll, this); if(g.enableColumnResize !== false){ this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom); this.splitZone.setOuterHandleElId(Ext.id(this.lockedHd.dom)); this.splitZone.setOuterHandleElId(Ext.id(this.mainHd.dom)); } if(g.enableColumnMove){ this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd); this.columnDrag.setOuterHandleElId(Ext.id(this.lockedInnerHd)); this.columnDrag.setOuterHandleElId(Ext.id(this.innerHd)); this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom); } if(g.enableHdMenu !== false){ this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'}); this.hmenu.add( {itemId: 'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'}, {itemId: 'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'} ); if(this.grid.enableColLock !== false){ this.hmenu.add('-', {itemId: 'lock', text: this.lockText, cls: 'xg-hmenu-lock'}, {itemId: 'unlock', text: this.unlockText, cls: 'xg-hmenu-unlock'} ); } if(g.enableColumnHide !== false){ this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'}); this.colMenu.on({ scope: this, beforeshow: this.beforeColMenuShow, itemclick: this.handleHdMenuClick }); this.hmenu.add('-', { itemId:'columns', hideOnClick: false, text: this.columnsText, menu: this.colMenu, iconCls: 'x-cols-icon' }); } this.hmenu.on('itemclick', this.handleHdMenuClick, this); } if(g.trackMouseOver){ this.mainBody.on({ scope: this, mouseover: this.onRowOver, mouseout: this.onRowOut }); this.lockedBody.on({ scope: this, mouseover: this.onRowOver, mouseout: this.onRowOut }); } if(g.enableDragDrop || g.enableDrag){ this.dragZone = new Ext.grid.GridDragZone(g, { ddGroup : g.ddGroup || 'GridDD' }); } this.updateHeaderSortState(); }, layout : function(){ if(!this.mainBody){ return; } var g = this.grid; var c = g.getGridEl(); var csize = c.getSize(true); var vw = csize.width; if(!g.hideHeaders && (vw < 20 || csize.height < 20)){ return; } this.syncHeaderHeight(); if(g.autoHeight){ this.scroller.dom.style.overflow = 'visible'; this.lockedScroller.dom.style.overflow = 'visible'; if(Ext.isWebKit){ this.scroller.dom.style.position = 'static'; this.lockedScroller.dom.style.position = 'static'; } }else{ this.el.setSize(csize.width, csize.height); var hdHeight = this.mainHd.getHeight(); var vh = csize.height - (hdHeight); } this.updateLockedWidth(); if(this.forceFit){ if(this.lastViewWidth != vw){ this.fitColumns(false, false); this.lastViewWidth = vw; } }else { this.autoExpand(); this.syncHeaderScroll(); } this.onLayout(vw, vh); }, getOffsetWidth : function() { return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth() + this.getScrollOffset()) + 'px'; }, renderHeaders : function(){ var cm = this.cm, ts = this.templates, ct = ts.hcell, cb = [], lcb = [], p = {}, len = cm.getColumnCount(), last = len - 1; for(var i = 0; i < len; i++){ p.id = cm.getColumnId(i); p.value = cm.getColumnHeader(i) || ''; p.style = this.getColumnStyle(i, true); p.tooltip = this.getColumnTooltip(i); p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) + (cm.config[i].headerCls ? ' ' + cm.config[i].headerCls : ''); if(cm.config[i].align == 'right'){ p.istyle = 'padding-right:16px'; } else { delete p.istyle; } if(cm.isLocked(i)){ lcb[lcb.length] = ct.apply(p); }else{ cb[cb.length] = ct.apply(p); } } return [ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'}), ts.header.apply({cells: lcb.join(''), tstyle:'width:'+this.getLockedWidth()+';'})]; }, updateHeaders : function(){ var hd = this.renderHeaders(); this.innerHd.firstChild.innerHTML = hd[0]; this.innerHd.firstChild.style.width = this.getOffsetWidth(); this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth(); this.lockedInnerHd.firstChild.innerHTML = hd[1]; var lw = this.getLockedWidth(); this.lockedInnerHd.firstChild.style.width = lw; this.lockedInnerHd.firstChild.firstChild.style.width = lw; }, getResolvedXY : function(resolved){ if(!resolved){ return null; } var c = resolved.cell, r = resolved.row; return c ? Ext.fly(c).getXY() : [this.scroller.getX(), Ext.fly(r).getY()]; }, syncFocusEl : function(row, col, hscroll){ Ext.ux.grid.LockingGridView.superclass.syncFocusEl.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll); }, ensureVisible : function(row, col, hscroll){ return Ext.ux.grid.LockingGridView.superclass.ensureVisible.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll); }, insertRows : function(dm, firstRow, lastRow, isUpdate){ var last = dm.getCount() - 1; if(!isUpdate && firstRow === 0 && lastRow >= last){ this.refresh(); }else{ if(!isUpdate){ this.fireEvent('beforerowsinserted', this, firstRow, lastRow); } var html = this.renderRows(firstRow, lastRow), before = this.getRow(firstRow); if(before){ if(firstRow === 0){ this.removeRowClass(0, this.firstRowCls); } Ext.DomHelper.insertHtml('beforeBegin', before, html[0]); before = this.getLockedRow(firstRow); Ext.DomHelper.insertHtml('beforeBegin', before, html[1]); }else{ this.removeRowClass(last - 1, this.lastRowCls); Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]); Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]); } if(!isUpdate){ this.fireEvent('rowsinserted', this, firstRow, lastRow); this.processRows(firstRow); }else if(firstRow === 0 || firstRow >= last){ this.addRowClass(firstRow, firstRow === 0 ? this.firstRowCls : this.lastRowCls); } } this.syncFocusEl(firstRow); }, getColumnStyle : function(col, isHeader){ var style = !isHeader ? this.cm.config[col].cellStyle || this.cm.config[col].css || '' : this.cm.config[col].headerStyle || ''; style += 'width:'+this.getColumnWidth(col)+';'; if(this.cm.isHidden(col)){ style += 'display:none;'; } var align = this.cm.config[col].align; if(align){ style += 'text-align:'+align+';'; } return style; }, getLockedWidth : function() { return this.cm.getTotalLockedWidth() + 'px'; }, getTotalWidth : function() { return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth()) + 'px'; }, getColumnData : function(){ var cs = [], cm = this.cm, colCount = cm.getColumnCount(); for(var i = 0; i < colCount; i++){ var name = cm.getDataIndex(i); cs[i] = { name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name), renderer : cm.getRenderer(i), id : cm.getColumnId(i), style : this.getColumnStyle(i), locked : cm.isLocked(i) }; } return cs; }, renderBody : function(){ var markup = this.renderRows() || [' ', ' ']; return [this.templates.body.apply({rows: markup[0]}), this.templates.body.apply({rows: markup[1]})]; }, refreshRow: function(record){ var store = this.ds, colCount = this.cm.getColumnCount(), columns = this.getColumnData(), last = colCount - 1, cls = ['x-grid3-row'], rowParams = { tstyle: String.format("width: {0};", this.getTotalWidth()) }, lockedRowParams = { tstyle: String.format("width: {0};", this.getLockedWidth()) }, colBuffer = [], lockedColBuffer = [], cellTpl = this.templates.cell, rowIndex, row, lockedRow, column, meta, css, i; if (Ext.isNumber(record)) { rowIndex = record; record = store.getAt(rowIndex); } else { rowIndex = store.indexOf(record); } if (!record || rowIndex < 0) { return; } for (i = 0; i < colCount; i++) { column = columns[i]; if (i == 0) { css = 'x-grid3-cell-first'; } else { css = (i == last) ? 'x-grid3-cell-last ' : ''; } meta = { id: column.id, style: column.style, css: css, attr: "", cellAttr: "" }; meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store); if (Ext.isEmpty(meta.value)) { meta.value = ' '; } if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') { meta.css += ' x-grid3-dirty-cell'; } if (column.locked) { lockedColBuffer[i] = cellTpl.apply(meta); } else { colBuffer[i] = cellTpl.apply(meta); } } row = this.getRow(rowIndex); row.className = ''; lockedRow = this.getLockedRow(rowIndex); lockedRow.className = ''; if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) { cls.push('x-grid3-row-alt'); } if (this.getRowClass) { rowParams.cols = colCount; cls.push(this.getRowClass(record, rowIndex, rowParams, store)); } // Unlocked rows this.fly(row).addClass(cls).setStyle(rowParams.tstyle); rowParams.cells = colBuffer.join(""); row.innerHTML = this.templates.rowInner.apply(rowParams); // Locked rows this.fly(lockedRow).addClass(cls).setStyle(lockedRowParams.tstyle); lockedRowParams.cells = lockedColBuffer.join(""); lockedRow.innerHTML = this.templates.rowInner.apply(lockedRowParams); lockedRow.rowIndex = rowIndex; this.syncRowHeights(row, lockedRow); this.fireEvent('rowupdated', this, rowIndex, record); }, refresh : function(headersToo){ this.fireEvent('beforerefresh', this); this.grid.stopEditing(true); var result = this.renderBody(); this.mainBody.update(result[0]).setWidth(this.getTotalWidth()); this.lockedBody.update(result[1]).setWidth(this.getLockedWidth()); if(headersToo === true){ this.updateHeaders(); this.updateHeaderSortState(); } this.processRows(0, true); this.layout(); this.applyEmptyText(); this.fireEvent('refresh', this); }, onDenyColumnLock : function(){ }, initData : function(ds, cm){ if(this.cm){ this.cm.un('columnlockchange', this.onColumnLock, this); } Ext.ux.grid.LockingGridView.superclass.initData.call(this, ds, cm); if(this.cm){ this.cm.on('columnlockchange', this.onColumnLock, this); } }, onColumnLock : function(){ this.refresh(true); }, handleHdMenuClick : function(item){ var index = this.hdCtxIndex, cm = this.cm, id = item.getItemId(), llen = cm.getLockedCount(); switch(id){ case 'lock': if(cm.getColumnCount(true) <= llen + 1){ this.onDenyColumnLock(); return undefined; } cm.setLocked(index, true); if(llen != index){ cm.moveColumn(index, llen); this.grid.fireEvent('columnmove', index, llen); } break; case 'unlock': if(llen - 1 != index){ cm.setLocked(index, false, true); cm.moveColumn(index, llen - 1); this.grid.fireEvent('columnmove', index, llen - 1); }else{ cm.setLocked(index, false); } break; default: return Ext.ux.grid.LockingGridView.superclass.handleHdMenuClick.call(this, item); } return true; }, handleHdDown : function(e, t){ Ext.ux.grid.LockingGridView.superclass.handleHdDown.call(this, e, t); if(this.grid.enableColLock !== false){ if(Ext.fly(t).hasClass('x-grid3-hd-btn')){ var hd = this.findHeaderCell(t), index = this.getCellIndex(hd), ms = this.hmenu.items, cm = this.cm; ms.get('lock').setDisabled(cm.isLocked(index)); ms.get('unlock').setDisabled(!cm.isLocked(index)); } } }, syncHeaderHeight: function(){ var hrow = Ext.fly(this.innerHd).child('tr', true), lhrow = Ext.fly(this.lockedInnerHd).child('tr', true); hrow.style.height = 'auto'; lhrow.style.height = 'auto'; var hd = hrow.offsetHeight, lhd = lhrow.offsetHeight, height = Math.max(lhd, hd) + 'px'; hrow.style.height = height; lhrow.style.height = height; }, updateLockedWidth: function(){ var lw = this.cm.getTotalLockedWidth(), tw = this.cm.getTotalWidth() - lw, csize = this.grid.getGridEl().getSize(true), lp = Ext.isBorderBox ? 0 : this.lockedBorderWidth, rp = Ext.isBorderBox ? 0 : this.rowBorderWidth, vw = (csize.width - lw - lp - rp) + 'px', so = this.getScrollOffset(); if(!this.grid.autoHeight){ var vh = (csize.height - this.mainHd.getHeight()) + 'px'; this.lockedScroller.dom.style.height = vh; this.scroller.dom.style.height = vh; } this.lockedWrap.dom.style.width = (lw + rp) + 'px'; this.scroller.dom.style.width = vw; this.mainWrap.dom.style.left = (lw + lp + rp) + 'px'; if(this.innerHd){ this.lockedInnerHd.firstChild.style.width = lw + 'px'; this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px'; this.innerHd.style.width = vw; this.innerHd.firstChild.style.width = (tw + rp + so) + 'px'; this.innerHd.firstChild.firstChild.style.width = tw + 'px'; } if(this.mainBody){ this.lockedBody.dom.style.width = (lw + rp) + 'px'; this.mainBody.dom.style.width = (tw + rp) + 'px'; } } }); Ext.ux.grid.LockingColumnModel = Ext.extend(Ext.grid.ColumnModel, {
    /** * Returns true if the given column index is currently locked * @param {Number} colIndex The column index * @return {Boolean} True if the column is locked */ isLocked : function(colIndex){ return this.config[colIndex].locked === true; },
    /** * Locks or unlocks a given column * @param {Number} colIndex The column index * @param {Boolean} value True to lock, false to unlock * @param {Boolean} suppressEvent Pass false to cause the columnlockchange event not to fire */ setLocked : function(colIndex, value, suppressEvent){ if (this.isLocked(colIndex) == value) { return; } this.config[colIndex].locked = value; if (!suppressEvent) { this.fireEvent('columnlockchange', this, colIndex, value); } },
    /** * Returns the total width of all locked columns * @return {Number} The width of all locked columns */ getTotalLockedWidth : function(){ var totalWidth = 0; for (var i = 0, len = this.config.length; i < len; i++) { if (this.isLocked(i) && !this.isHidden(i)) { totalWidth += this.getColumnWidth(i); } } return totalWidth; },
    /** * Returns the total number of locked columns * @return {Number} The number of locked columns */ getLockedCount : function() { var len = this.config.length; for (var i = 0; i < len; i++) { if (!this.isLocked(i)) { return i; } } //if we get to this point all of the columns are locked so we return the total return len; },
    /** * Moves a column from one position to another * @param {Number} oldIndex The current column index * @param {Number} newIndex The destination column index */ moveColumn : function(oldIndex, newIndex){ var oldLocked = this.isLocked(oldIndex), newLocked = this.isLocked(newIndex); if (oldIndex < newIndex && oldLocked && !newLocked) { this.setLocked(oldIndex, false, true); } else if (oldIndex > newIndex && !oldLocked && newLocked) { this.setLocked(oldIndex, true, true); } Ext.ux.grid.LockingColumnModel.superclass.moveColumn.apply(this, arguments); } });Ext.ns('Ext.ux.form'); /** * @class Ext.ux.form.MultiSelect * @extends Ext.form.Field * A control that allows selection and form submission of multiple list items. * * @history * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams) * 2008-06-19 bpm Docs and demo code clean up * * @constructor * Create a new MultiSelect * @param {Object} config Configuration options * @xtype multiselect */ Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field, {
    /** * @cfg {String} legend Wraps the object with a fieldset and specified legend. */
    /** * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list. */
    /** * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined). */
    /** * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined). */
    /** * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false). */ ddReorder:false,
    /** * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a * toolbar config, or an array of buttons/button configs to be added to the toolbar. */
    /** * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled * (use for lists which are sorted, defaults to false). */ appendOnly:false,
    /** * @cfg {Number} width Width in pixels of the control (defaults to 100). */ width:100,
    /** * @cfg {Number} height Height in pixels of the control (defaults to 100). */ height:100,
    /** * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0). */ displayField:0,
    /** * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1). */ valueField:1,
    /** * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no * selection (defaults to true). */ allowBlank:true,
    /** * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0). */ minSelections:0,
    /** * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE). */ maxSelections:Number.MAX_VALUE,
    /** * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as * {@link Ext.form.TextField#blankText}. */ blankText:Ext.form.TextField.prototype.blankText,
    /** * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0} * item(s) required'). The {0} token will be replaced by the value of {@link #minSelections}. */ minSelectionsText:'Minimum {0} item(s) required',
    /** * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0} * item(s) allowed'). The {0} token will be replaced by the value of {@link #maxSelections}. */ maxSelectionsText:'Maximum {0} item(s) allowed',
    /** * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values * (defaults to ','). */ delimiter:',',
    /** * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to undefined). * 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 }); fs.body.addClass('ux-mselect'); 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 || this.readOnly) { 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/** * Sets a delimited string (using {@link #delimiter}) or array of data values into the list. * @param {String/Array} values The values to set */ setValue: function(values) { var index; var selections = []; this.view.clearSelections(); this.hiddenField.dom.value = ''; if (!values || (values == '')) { return; } if (!Ext.isArray(values)) { values = values.split(this.delimiter); } 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); if(this.ms.appendOnly) { insertAt = this.view.store.getCount(); } else { insertAt = n == this.ms.fs.body.dom ? this.view.store.getCount() - 1 : 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(v1, v2){ return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0); }; 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 = Ext.extend(Ext.dd.DropTarget, { constructor : 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; }, 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, panel = dd.panel, 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(); panel.el.dom.parentNode.removeChild(dd.panel.el.dom); if(pos !== false){ c.insert(pos, panel); }else{ c.add(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.mon(pb.getEl().applyStyles('cursor:pointer'), 'click', this.handleProgressBarClick, this); }, this, {single: true}); } }, // private // This method handles the click for the progress bar handleProgressBarClick : function(e){ var parent = this.parent, displayItem = parent.displayItem, box = this.progressBar.getBox(), xy = e.getXY(), position = xy[0]-box.x, pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize), 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(), pgData = this.getPageData(), pageNum = this.readPage(pgData), 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, saveText: 'Save', cancelText: 'Cancel', commitChangesText: 'You need to commit or cancel your changes', errorText: 'Errors', 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 canceledit * Fired when the editor is cancelled. * @param {Ext.ux.grid.RowEditor} roweditor This object * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid. */ 'canceledit',
    /** * @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, beforedestroy : this.beforedestroy, 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, [])); }, beforedestroy: function() { this.stopMonitoring(); this.grid.getStore().un('remove', this.onStoreRemove, this); this.stopEditing(false); Ext.destroy(this.btns, this.tooltip); }, 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(this.commitChangesText); return; } if(Ext.isObject(rowIndex)){ rowIndex = this.grid.getStore().indexOf(rowIndex); } if(this.fireEvent('beforeedit', this, rowIndex) !== false){ this.editing = true; var g = this.grid, view = g.getView(), row = view.getRow(rowIndex), 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] = Ext.isEmpty(val) ? '' : 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(); this.fireEvent('canceledit', this, saveChanges === false); return; } var changes = {}, r = this.record, hasChange = false, 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], 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(); Ext.iterate(changes, function(name, value){ r.set(name, value); }); 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() + 9 : 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 === (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), 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{ if (Ext.isIE) { ed.margins = pm('0 0 2 0'); } else { ed.margins = pm('0 1 2 0'); } } 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, width: this.minButtonWidth, handler: this.stopEditing.createDelegate(this, [true]) }, { xtype: 'button', text: this.cancelText, 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 g = this.grid, h = this.el.dom.clientHeight, view = g.getView(), scroll = view.scroller.dom.scrollLeft, bw = this.btns.getWidth(), width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth()); 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, cm = this.grid.getColumnModel(), c; if(pt){ index = this.getTargetColumnIndex(pt); } for(var i = index||0, len = cm.getColumnCount(); i < len; i++){ c = cm.getColumnAt(i); if(!c.hidden && c.getEditor()){ c.getEditor().focus(); break; } } } }, getTargetColumnIndex: function(pt){ var grid = this.grid, v = grid.view, x = pt.left, cms = grid.colModel.config, 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); }, lastVisibleColumn : function() { var i = this.items.getCount() - 1, c; for(; i >= 0; i--) { c = this.items.items[i]; if (!c.hidden) { return c; } } }, showTooltip: function(msg){ var t = this.tooltip; if(!t){ t = this.tooltip = new Ext.ToolTip({ maxWidth: 600, cls: 'errorTip', width: 300, title: this.errorText, autoHide: false, anchor: 'left', anchorToTarget: true, mouseOffset: [40,0] }); } var v = this.grid.getView(), top = parseInt(this.el.dom.style.top, 10), scroll = v.scroller.dom.scrollTop, h = this.el.getHeight(); if(top + h >= scroll){ t.initTarget(this.lastVisibleColumn().getEl()); if(!t.rendered){ t.show(); t.hide(); } t.body.update(msg); t.doAutoWidth(20); t.show(); }else if(t.rendered){ t.hide(); } }, getErrorText: function(){ var data = ['
      ']; this.items.each(function(f){ if(!f.isValid(true)){ data.push('
    • ', f.getActiveError(), '
    • '); } }); data.push('
    '); return data; } }); Ext.preg('roweditor', Ext.ux.grid.RowEditor); 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, hideable: false, 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() { if(this.keyNav){ this.keyNav.disable(); delete this.keyNav; } /* * A majority of the time, the plugin will be destroyed along with the grid, * which means the mainBody won't be available. On the off chance that the plugin * isn't destroyed with the grid, take care of removing the listener. */ var mainBody = this.grid.getView().mainBody; if(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, type: 'row', // private allowContainerRemove: false, // private isValidParent : function(c, target){ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom; }, getLayoutTargetSize : function() { var target = this.container.getLayoutTarget(), ret; if (target) { ret = target.getViewSize(); // IE in strict mode will return a height of 0 on the 1st pass of getViewSize. // Use getStyleSize to verify the 0 height, the adjustment pass will then work properly // with getViewSize if (Ext.isIE && Ext.isStrict && ret.height == 0){ ret = target.getStyleSize(); } ret.width -= target.getPadding('lr'); ret.height -= target.getPadding('tb'); } return ret; }, renderAll : function(ct, target) { if(!this.innerCt){ // the innerCt prevents wrapping and shuffling while // the container is resizing this.innerCt = target.createChild({cls:'x-column-inner'}); this.innerCt.createChild({cls:'x-clear'}); } Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt); }, // private onLayout : function(ct, target){ var rs = ct.items.items, len = rs.length, r, m, i, margins = []; this.renderAll(ct, target); var size = this.getLayoutTargetSize(); if(size.width < 1 && size.height < 1){ // display none? return; } var h = size.height, 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]; m = r.getPositionEl().getMargins('tb'); margins[i] = m; if(!r.rowHeight){ ph -= (r.getHeight() + m); } } ph = ph < 0 ? 0 : ph; for(i = 0; i < len; i++){ r = rs[i]; m = margins[i]; if(r.rowHeight){ r.setSize({height: Math.floor(r.rowHeight*ph) - m}); } } // Browsers differ as to when they account for scrollbars. We need to re-measure to see if the scrollbar // spaces were accounted for properly. If not, re-layout. if (Ext.isIE) { if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) { var ts = this.getLayoutTargetSize(); if (ts.width != size.width){ this.adjustmentPass = true; this.onLayout(ct, target); } } } delete this.adjustmentPass; }
    /** * @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.initList(); 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); } }, afterRender : function() { Ext.ux.form.SelectBox.superclass.afterRender.apply(this, arguments); if(Ext.isWebKit) { 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 = Ext.isNumber(record) ? record : this.store.indexOf(record); this.select(index, this.isExpanded()); this.onSelect(this.store.getAt(index), 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;
    /** * Plugin for PagingToolbar which replaces the textfield input with a slider */ Ext.ux.SlidingPager = Ext.extend(Object, { init : function(pbar){ var idx = pbar.items.indexOf(pbar.inputItem); Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c){ c.hide(); }); var slider = new Ext.Slider({ width: 114, minValue: 1, maxValue: 1, plugins: new Ext.slider.Tip({ getText : function(thumb) { return String.format('Page {0} of {1}', thumb.value, thumb.slider.maxValue); } }), listeners: { changecomplete: function(s, v){ pbar.changePage(v); } } }); pbar.insert(idx + 1, slider); pbar.on({ change: function(pb, data){ slider.setMaxValue(data.pages); slider.setValue(data.activePage); } }); } });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, { actionMode: 'wrap', 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})); }, // 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.disabled = false; this.wrap.removeClass(this.field.disabledClass); } }, doDisable: function(){ if (this.wrap) { this.disabled = true; this.wrap.addClass(this.field.disabledClass); this.el.removeClass(this.field.disabledClass); } }, doResize: function(w, h){ if (typeof w == 'number') { this.el.setWidth(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(); } if (this.mimicing){ Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this); } } }); //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.StatusBar *

    Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar * provides a greedy status element that can be aligned to either side and has convenient methods for setting the * status text and icon. You can also indicate that something is processing using the {@link #showBusy} method.

    *
    
    new Ext.Panel({
        title: 'StatusBar',
        // etc.
        bbar: new Ext.ux.StatusBar({
            id: 'my-status',
    
            // defaults to use when the status is cleared:
            defaultText: 'Default status text',
            defaultIconCls: 'default-icon',
    
            // values to set initially:
            text: 'Ready',
            iconCls: 'ready-icon',
    
            // any standard Toolbar items:
            items: [{
                text: 'A Button'
            }, '-', 'Plain Text']
        })
    });
    
    // Update the status bar later in code:
    var sb = Ext.getCmp('my-status');
    sb.setStatus({
        text: 'OK',
        iconCls: 'ok-icon',
        clear: true // auto-clear after a set interval
    });
    
    // Set the status bar to show that something is processing:
    sb.showBusy();
    
    // processing....
    
    sb.clearStatus(); // once completeed
    
    * @extends Ext.Toolbar * @constructor * Creates a new StatusBar * @param {Object/Array} config A config object */ Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
    /** * @cfg {String} statusAlign * The alignment of the status element within the overall StatusBar layout. When the StatusBar is rendered, * it creates an internal div containing the status text and icon. Any additional Toolbar items added in the * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be * rendered, in added order, to the opposite side. The status element is greedy, so it will automatically * expand to take up all sapce left over by any other items. Example usage: *
    
    // Create a left-aligned status bar containing a button,
    // separator and text item that will be right-aligned (default):
    new Ext.Panel({
        title: 'StatusBar',
        // etc.
        bbar: new Ext.ux.StatusBar({
            defaultText: 'Default status text',
            id: 'status-id',
            items: [{
                text: 'A Button'
            }, '-', 'Plain Text']
        })
    });
    
    // By adding the statusAlign config, this will create the
    // exact same toolbar, except the status and toolbar item
    // layout will be reversed from the previous example:
    new Ext.Panel({
        title: 'StatusBar',
        // etc.
        bbar: new Ext.ux.StatusBar({
            defaultText: 'Default status text',
            id: 'status-id',
            statusAlign: 'right',
            items: [{
                text: 'A Button'
            }, '-', 'Plain Text']
        })
    });
    
    */
    /** * @cfg {String} defaultText * The default {@link #text} value. This will be used anytime the status bar is cleared with the * useDefaults:true option (defaults to ''). */
    /** * @cfg {String} defaultIconCls * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon). * This will be used anytime the status bar is cleared with the useDefaults:true option (defaults to ''). */
    /** * @cfg {String} text * A string that will be initially set as the status message. This string * will be set as innerHTML (html tags are accepted) for the toolbar item. * If not specified, the value set for {@link #defaultText} * will be used. */
    /** * @cfg {String} iconCls * A CSS class that will be initially set as the status bar icon and is * expected to provide a background image (defaults to ''). * Example usage:
    
    // Example CSS rule:
    .x-statusbar .x-status-custom {
        padding-left: 25px;
        background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
    }
    
    // Setting a default icon:
    var sb = new Ext.ux.StatusBar({
        defaultIconCls: 'x-status-custom'
    });
    
    // Changing the icon:
    sb.setStatus({
        text: 'New status',
        iconCls: 'x-status-custom'
    });
    
    */
    /** * @cfg {String} cls * The base class applied to the containing element for this component on render (defaults to 'x-statusbar') */ cls : 'x-statusbar',
    /** * @cfg {String} busyIconCls * The default {@link #iconCls} applied when calling * {@link #showBusy} (defaults to 'x-status-busy'). * It can be overridden at any time by passing the iconCls * argument into {@link #showBusy}. */ busyIconCls : 'x-status-busy',
    /** * @cfg {String} busyText * The default {@link #text} applied when calling * {@link #showBusy} (defaults to 'Loading...'). * It can be overridden at any time by passing the text * argument into {@link #showBusy}. */ busyText : 'Loading...',
    /** * @cfg {Number} autoClear * The number of milliseconds to wait after setting the status via * {@link #setStatus} before automatically clearing the status * text and icon (defaults to 5000). Note that this only applies * when passing the clear argument to {@link #setStatus} * since that is the only way to defer clearing the status. This can * be overridden by specifying a different wait value in * {@link #setStatus}. Calls to {@link #clearStatus} * always clear the status bar immediately and ignore this value. */ autoClear : 5000,
    /** * @cfg {String} emptyText * The text string to use if no text has been set. Defaults to * ' '). If there are no other items in the toolbar using * an empty string ('') for this value would end up in the toolbar * height collapsing since the empty string will not maintain the toolbar * height. Use '' if the toolbar should collapse in height * vertically when no text is specified and there are no other items in * the toolbar. */ emptyText : ' ', // private activeThreadId : 0, // private initComponent : function(){ if(this.statusAlign=='right'){ this.cls += ' x-status-right'; } Ext.ux.StatusBar.superclass.initComponent.call(this); }, // private afterRender : function(){ Ext.ux.StatusBar.superclass.afterRender.call(this); var right = this.statusAlign == 'right'; this.currIconCls = this.iconCls || this.defaultIconCls; this.statusEl = new Ext.Toolbar.TextItem({ cls: 'x-status-text ' + (this.currIconCls || ''), text: this.text || this.defaultText || '' }); if(right){ this.add('->'); this.add(this.statusEl); }else{ this.insert(0, this.statusEl); this.insert(1, '->'); } this.doLayout(); },
    /** * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the * status that was set after a specified interval. * @param {Object/String} config A config object specifying what status to set, or a string assumed * to be the status text (and all other options are defaulted as explained below). A config * object containing any or all of the following properties can be passed:
      *
    • text {String} : (optional) The status text to display. If not specified, any current * status text will remain unchanged.
    • *
    • iconCls {String} : (optional) The CSS class used to customize the status icon (see * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.
    • *
    • clear {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not * specified, the new status will not be auto-cleared and will stay until updated again or cleared using * {@link #clearStatus}. If true is passed, the status will be cleared using {@link #autoClear}, * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed, * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value. * All other options will be defaulted as with the boolean option. To customize any other options, * you can pass an object in the format:
        *
      • wait {Number} : (optional) The number of milliseconds to wait before clearing * (defaults to {@link #autoClear}).
      • *
      • anim {Number} : (optional) False to clear the status immediately once the callback * executes (defaults to true which fades the status out).
      • *
      • useDefaults {Number} : (optional) False to completely clear the status text and iconCls * (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).
      • *
    * Example usage:
    
    // Simple call to update the text
    statusBar.setStatus('New status');
    
    // Set the status and icon, auto-clearing with default options:
    statusBar.setStatus({
        text: 'New status',
        iconCls: 'x-status-custom',
        clear: true
    });
    
    // Auto-clear with custom options:
    statusBar.setStatus({
        text: 'New status',
        iconCls: 'x-status-custom',
        clear: {
            wait: 8000,
            anim: false,
            useDefaults: false
        }
    });
    
    * @return {Ext.ux.StatusBar} this */ setStatus : function(o){ o = o || {}; if(typeof o == 'string'){ o = {text:o}; } if(o.text !== undefined){ this.setText(o.text); } if(o.iconCls !== undefined){ this.setIcon(o.iconCls); } if(o.clear){ var c = o.clear, wait = this.autoClear, defaults = {useDefaults: true, anim: true}; if(typeof c == 'object'){ c = Ext.applyIf(c, defaults); if(c.wait){ wait = c.wait; } }else if(typeof c == 'number'){ wait = c; c = defaults; }else if(typeof c == 'boolean'){ c = defaults; } c.threadId = this.activeThreadId; this.clearStatus.defer(wait, this, [c]); } return this; },
    /** * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation. * @param {Object} config (optional) A config object containing any or all of the following properties. If this * object is not specified the status will be cleared using the defaults below:
      *
    • anim {Boolean} : (optional) True to clear the status by fading out the status element (defaults * to false which clears immediately).
    • *
    • useDefaults {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).
    • *
    * @return {Ext.ux.StatusBar} this */ clearStatus : function(o){ o = o || {}; if(o.threadId && o.threadId !== this.activeThreadId){ // this means the current call was made internally, but a newer // thread has set a message since this call was deferred. Since // we don't want to overwrite a newer message just ignore. return this; } var text = o.useDefaults ? this.defaultText : this.emptyText, iconCls = o.useDefaults ? (this.defaultIconCls ? this.defaultIconCls : '') : ''; if(o.anim){ // animate the statusEl Ext.Element this.statusEl.el.fadeOut({ remove: false, useDisplay: true, scope: this, callback: function(){ this.setStatus({ text: text, iconCls: iconCls }); this.statusEl.el.show(); } }); }else{ // hide/show the el to avoid jumpy text or icon this.statusEl.hide(); this.setStatus({ text: text, iconCls: iconCls }); this.statusEl.show(); } return this; },
    /** * Convenience method for setting the status text directly. For more flexible options see {@link #setStatus}. * @param {String} text (optional) The text to set (defaults to '') * @return {Ext.ux.StatusBar} this */ setText : function(text){ this.activeThreadId++; this.text = text || ''; if(this.rendered){ this.statusEl.setText(this.text); } return this; },
    /** * Returns the current status text. * @return {String} The status text */ getText : function(){ return this.text; },
    /** * Convenience method for setting the status icon directly. For more flexible options see {@link #setStatus}. * See {@link #iconCls} for complete details about customizing the icon. * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed) * @return {Ext.ux.StatusBar} this */ setIcon : function(cls){ this.activeThreadId++; cls = cls || ''; if(this.rendered){ if(this.currIconCls){ this.statusEl.removeClass(this.currIconCls); this.currIconCls = null; } if(cls.length > 0){ this.statusEl.addClass(cls); this.currIconCls = cls; } }else{ this.currIconCls = cls; } return this; },
    /** * Convenience method for setting the status text and icon to special values that are pre-configured to indicate * a "busy" state, usually for loading or processing activities. * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a * string to use as the status text (in which case all other options for setStatus will be defaulted). Use the * text and/or iconCls properties on the config to override the default {@link #busyText} * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}. * @return {Ext.ux.StatusBar} this */ showBusy : function(o){ if(typeof o == 'string'){ o = {text:o}; } o = Ext.applyIf(o || {}, { text: this.busyText, iconCls: this.busyIconCls }); return this.setStatus(o); } }); Ext.reg('statusbar', Ext.ux.StatusBar); /** * @class Ext.ux.TabCloseMenu * @extends Object * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs. Note that the menu respects * the closable configuration on the tab. As such, commands like remove others and remove all will not * remove items that are not closable. * * @constructor * @param {Object} config The configuration options * @ptype tabclosemenu */ Ext.ux.TabCloseMenu = Ext.extend(Object, {
    /** * @cfg {String} closeTabText * The text for closing the current tab. Defaults to 'Close Tab'. */ closeTabText: 'Close Tab',
    /** * @cfg {String} closeOtherTabsText * The text for closing all tabs except the current one. Defaults to 'Close Other Tabs'. */ closeOtherTabsText: 'Close Other Tabs',
    /** * @cfg {Boolean} showCloseAll * Indicates whether to show the 'Close All' option. Defaults to true. */ showCloseAll: true,
    /** * @cfg {String} closeAllTabsText *

    The text for closing all tabs. Defaults to 'Close All Tabs'. */ closeAllTabsText: 'Close All Tabs', constructor : function(config){ Ext.apply(this, config || {}); }, //public init : function(tabs){ this.tabs = tabs; tabs.on({ scope: this, contextmenu: this.onContextMenu, destroy: this.destroy }); }, destroy : function(){ Ext.destroy(this.menu); delete this.menu; delete this.tabs; delete this.active; }, // private onContextMenu : function(tabs, item, e){ this.active = item; var m = this.createMenu(), disableAll = true, disableOthers = true, closeAll = m.getComponent('closeall'); m.getComponent('close').setDisabled(!item.closable); tabs.items.each(function(){ if(this.closable){ disableAll = false; if(this != item){ disableOthers = false; return false; } } }); m.getComponent('closeothers').setDisabled(disableOthers); if(closeAll){ closeAll.setDisabled(disableAll); } e.stopEvent(); m.showAt(e.getPoint()); }, createMenu : function(){ if(!this.menu){ var items = [{ itemId: 'close', text: this.closeTabText, scope: this, handler: this.onClose }]; if(this.showCloseAll){ items.push('-'); } items.push({ itemId: 'closeothers', text: this.closeOtherTabsText, scope: this, handler: this.onCloseOthers }); if(this.showCloseAll){ items.push({ itemId: 'closeall', text: this.closeAllTabsText, scope: this, handler: this.onCloseAll }); } this.menu = new Ext.menu.Menu({ items: items }); } return this.menu; }, onClose : function(){ this.tabs.remove(this.active); }, onCloseOthers : function(){ this.doClose(true); }, onCloseAll : function(){ this.doClose(false); }, doClose : function(excludeActive){ var items = []; this.tabs.items.each(function(item){ if(item.closable){ if(!excludeActive || item != this.active){ items.push(item); } } }, this); Ext.each(items, function(item){ this.tabs.remove(item); }, this); } }); 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.ns('Ext.ux');
    /** * @class Ext.ux.TabScrollerMenu * @extends Object * Plugin (ptype = 'tabscrollermenu') for adding a tab scroller menu to tabs. * @constructor * @param {Object} config Configuration options * @ptype tabscrollermenu */ Ext.ux.TabScrollerMenu = Ext.extend(Object, {
    /** * @cfg {Number} pageSize How many items to allow per submenu. */ pageSize : 10,
    /** * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be. */ maxText : 15,
    /** * @cfg {String} menuPrefixText Text to prefix the submenus. */ menuPrefixText : 'Items', constructor : function(config) { config = config || {}; Ext.apply(this, config); }, //private init : function(tabPanel) { Ext.apply(tabPanel, this.parentOverrides); 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(); }); },
    /** * Returns an the current page size (this.pageSize); * @return {Number} this.pageSize The current page size. */ getPageSize : function() { return this.pageSize; },
    /** * Sets the number of menu items per submenu "page size". * @param {Number} pageSize The page size */ setPageSize : function(pageSize) { this.pageSize = pageSize; },
    /** * Returns the current maxText length; * @return {Number} this.maxText The current max text length. */ getMaxText : function() { return this.maxText; },
    /** * Sets the maximum text size for each menu item. * @param {Number} t The max text per each menu item. */ setMaxText : function(t) { this.maxText = t; },
    /** * Returns the current menu prefix text String.; * @return {String} this.menuPrefixText The current menu prefix text. */ getMenuPrefixText : function() { return this.menuPrefixText; },
    /** * Sets the menu prefix text String. * @param {String} t The menu prefix text. */ setMenuPrefixText : function(t) { this.menuPrefixText = t; }, // private && applied to the tab panel itself. parentOverrides : { // all execute within the scope of the tab panel // private showTabsMenu : function(e) { if (this.tabsMenu) { this.tabsMenu.destroy(); this.un('destroy', this.tabsMenu.destroy, this.tabsMenu); this.tabsMenu = null; } this.tabsMenu = new Ext.menu.Menu(); this.on('destroy', this.tabsMenu.destroy, this.tabsMenu); 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) { this.tabsMenu.add(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.reg('tabscrollermenu', Ext.ux.TabScrollerMenu); 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, root = xmlData.documentElement || xmlData; try{ node.beginUpdate(); node.appendChild(this.parseXml(root)); node.endUpdate(); this.runCallback(callback, scope || node, [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; /** * @class Ext.ux.ValidationStatus * A {@link Ext.StatusBar} plugin that provides automatic error notification when the * associated form contains validation errors. * @extends Ext.Component * @constructor * Creates a new ValiationStatus plugin * @param {Object} config A config object */ Ext.ux.ValidationStatus = Ext.extend(Ext.Component, {
    /** * @cfg {String} errorIconCls * The {@link #iconCls} value to be applied to the status message when there is a * validation error. Defaults to 'x-status-error'. */ errorIconCls : 'x-status-error',
    /** * @cfg {String} errorListCls * The css class to be used for the error list when there are validation errors. * Defaults to 'x-status-error-list'. */ errorListCls : 'x-status-error-list',
    /** * @cfg {String} validIconCls * The {@link #iconCls} value to be applied to the status message when the form * validates. Defaults to 'x-status-valid'. */ validIconCls : 'x-status-valid',
    /** * @cfg {String} showText * The {@link #text} value to be applied when there is a form validation error. * Defaults to 'The form has errors (click for details...)'. */ showText : 'The form has errors (click for details...)',
    /** * @cfg {String} showText * The {@link #text} value to display when the error list is displayed. * Defaults to 'Click again to hide the error list'. */ hideText : 'Click again to hide the error list',
    /** * @cfg {String} submitText * The {@link #text} value to be applied when the form is being submitted. * Defaults to 'Saving...'. */ submitText : 'Saving...', // private init : function(sb){ sb.on('render', function(){ this.statusBar = sb; this.monitor = true; this.errors = new Ext.util.MixedCollection(); this.listAlign = (sb.statusAlign=='right' ? 'br-tr?' : 'bl-tl?'); if(this.form){ this.form = Ext.getCmp(this.form).getForm(); this.startMonitoring(); this.form.on('beforeaction', function(f, action){ if(action.type == 'submit'){ // Ignore monitoring while submitting otherwise the field validation // events cause the status message to reset too early this.monitor = false; } }, this); var startMonitor = function(){ this.monitor = true; }; this.form.on('actioncomplete', startMonitor, this); this.form.on('actionfailed', startMonitor, this); } }, this, {single:true}); sb.on({ scope: this, afterlayout:{ single: true, fn: function(){ // Grab the statusEl after the first layout. sb.statusEl.getEl().on('click', this.onStatusClick, this, {buffer:200}); } }, beforedestroy:{ single: true, fn: this.onDestroy } }); }, // private startMonitoring : function(){ this.form.items.each(function(f){ f.on('invalid', this.onFieldValidation, this); f.on('valid', this.onFieldValidation, this); }, this); }, // private stopMonitoring : function(){ this.form.items.each(function(f){ f.un('invalid', this.onFieldValidation, this); f.un('valid', this.onFieldValidation, this); }, this); }, // private onDestroy : function(){ this.stopMonitoring(); this.statusBar.statusEl.un('click', this.onStatusClick, this); Ext.ux.ValidationStatus.superclass.onDestroy.call(this); }, // private onFieldValidation : function(f, msg){ if(!this.monitor){ return false; } if(msg){ this.errors.add(f.id, {field:f, msg:msg}); }else{ this.errors.removeKey(f.id); } this.updateErrorList(); if(this.errors.getCount() > 0){ if(this.statusBar.getText() != this.showText){ this.statusBar.setStatus({text:this.showText, iconCls:this.errorIconCls}); } }else{ this.statusBar.clearStatus().setIcon(this.validIconCls); } }, // private updateErrorList : function(){ if(this.errors.getCount() > 0){ var msg = '
      '; this.errors.each(function(err){ msg += ('
    • ' + err.msg + '
    • '); }, this); this.getMsgEl().update(msg+'
    '); }else{ this.getMsgEl().update(''); } }, // private getMsgEl : function(){ if(!this.msgEl){ this.msgEl = Ext.DomHelper.append(Ext.getBody(), { cls: this.errorListCls+' x-hide-offsets' }, true); this.msgEl.on('click', function(e){ var t = e.getTarget('li', 10, true); if(t){ Ext.getCmp(t.id.split('x-err-')[1]).focus(); this.hideErrors(); } }, this, {stopEvent:true}); // prevent anchor click navigation } return this.msgEl; }, // private showErrors : function(){ this.updateErrorList(); this.getMsgEl().alignTo(this.statusBar.getEl(), this.listAlign).slideIn('b', {duration:0.3, easing:'easeOut'}); this.statusBar.setText(this.hideText); this.form.getEl().on('click', this.hideErrors, this, {single:true}); // hide if the user clicks directly into the form }, // private hideErrors : function(){ var el = this.getMsgEl(); if(el.isVisible()){ el.slideOut('b', {duration:0.2, easing:'easeIn'}); this.statusBar.setText(this.showText); } this.form.getEl().un('click', this.hideErrors, this); }, // private onStatusClick : function(){ if(this.getMsgEl().isVisible()){ this.hideErrors(); }else if(this.errors.getCount() > 0){ this.showErrors(); } } });(function() { Ext.override(Ext.list.Column, { init : function() { var types = Ext.data.Types, st = this.sortType; if(this.type){ if(Ext.isString(this.type)){ this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO; } }else{ this.type = types.AUTO; } // named sortTypes are supported, here we look them up if(Ext.isString(st)){ this.sortType = Ext.data.SortTypes[st]; }else if(Ext.isEmpty(st)){ this.sortType = this.type.sortType; } } }); Ext.tree.Column = Ext.extend(Ext.list.Column, {}); Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {}); Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {}); Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {}); Ext.reg('tgcolumn', Ext.tree.Column); Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn); Ext.reg('tgdatecolumn', Ext.tree.DateColumn); Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn); })(); /** * @class Ext.ux.tree.TreeGridNodeUI * @extends Ext.tree.TreeNodeUI */ Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { isTreeGridNodeUI: true, renderElements : function(n, a, targetNode, bulkRender){ var t = n.getOwnerTree(), cols = t.columns, c = cols[0], i, buf, len; this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : ''; buf = [ '', '', '', '', this.indentMarkup, "", '', '', '', '', (c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text), '', '' ]; for(i = 1, len = cols.length; i < len; i++){ c = cols[i]; buf.push( '', '
    ', (c.tpl ? c.tpl.apply(a) : a[c.dataIndex]), '
    ', '' ); } buf.push( '', '' ); for(i = 0, len = cols.length; i'); } 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].firstChild.firstChild; 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; }, // private animExpand : function(cb){ this.ctNode.style.display = ""; Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb); } }); Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { isTreeGridNodeUI: true, // private render : function(){ if(!this.rendered){ this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom; this.node.expanded = true; } if(Ext.isWebKit) { // weird table-layout: fixed issue in webkit var ct = this.ctNode; ct.style.tableLayout = null; (function() { ct.style.tableLayout = 'fixed'; }).defer(1); } }, destroy : function(){ if(this.elNode){ Ext.dd.Registry.unregister(this.elNode.id); } delete this.node; }, collapse : Ext.emptyFn, expand : Ext.emptyFn });/** * @class Ext.tree.ColumnResizer * @extends Ext.util.Observable */ Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, {
    /** * @cfg {Number} minWidth The minimum width the column can be dragged to. * Defaults to 14. */ minWidth: 14, constructor: function(config){ Ext.apply(this, config); Ext.tree.ColumnResizer.superclass.constructor.call(this); }, init : function(tree){ this.tree = tree; tree.on('render', this.initEvents, this); }, initEvents : function(tree){ tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this); this.tracker = new Ext.dd.DragTracker({ onBeforeStart: this.onBeforeStart.createDelegate(this), onStart: this.onStart.createDelegate(this), onDrag: this.onDrag.createDelegate(this), onEnd: this.onEnd.createDelegate(this), tolerance: 3, autoStart: 300 }); this.tracker.initEl(tree.innerHd); tree.on('beforedestroy', this.tracker.destroy, this.tracker); }, handleHdMove : function(e, t){ var hw = 5, x = e.getPageX(), hd = e.getTarget('.x-treegrid-hd', 3, true); if(hd){ var r = hd.getRegion(), ss = hd.dom.style, pn = hd.dom.parentNode; if(x - r.left <= hw && hd.dom !== pn.firstChild) { var ps = hd.dom.previousSibling; while(ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) { ps = ps.previousSibling; } if(ps) { this.activeHd = Ext.get(ps); ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize'; } } else if(r.right - x <= hw) { var ns = hd.dom; while(ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) { ns = ns.previousSibling; } if(ns) { this.activeHd = Ext.get(ns); ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize'; } } else{ delete this.activeHd; ss.cursor = ''; } } }, onBeforeStart : function(e){ this.dragHd = this.activeHd; return !!this.dragHd; }, onStart : function(e){ this.dragHeadersDisabled = this.tree.headersDisabled; this.tree.headersDisabled = true; this.proxy = this.tree.body.createChild({cls:'x-treegrid-resizer'}); this.proxy.setHeight(this.tree.body.getHeight()); var x = this.tracker.getXY()[0]; this.hdX = this.dragHd.getX(); this.hdIndex = this.tree.findHeaderIndex(this.dragHd); this.proxy.setX(this.hdX); this.proxy.setWidth(x-this.hdX); this.maxWidth = this.tree.outerCt.getWidth() - this.tree.innerBody.translatePoints(this.hdX).left; }, onDrag : function(e){ var cursorX = this.tracker.getXY()[0]; this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth)); }, onEnd : function(e){ var nw = this.proxy.getWidth(), tree = this.tree, disabled = this.dragHeadersDisabled; this.proxy.remove(); delete this.dragHd; tree.columns[this.hdIndex].width = nw; tree.updateColumnWidths(); setTimeout(function(){ tree.headersDisabled = disabled; }, 100); } });Ext.ns('Ext.ux.tree'); /** * @class Ext.ux.tree.TreeGridSorter * @extends Ext.tree.TreeSorter * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}. The TreeGridSorter automatically monitors events on the * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange). * Example usage:
    *
    
     new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
         folderSort: true,
         dir: "desc",
         sortType: function(node) {
             // sort by a custom, typed attribute:
             return parseInt(node.id, 10);
         }
     });
     
    * @constructor * @param {TreeGrid} tree * @param {Object} config */ Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
    /** * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to ['sort-asc', 'sort-desc']) */ sortClasses : ['sort-asc', 'sort-desc'],
    /** * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to 'Sort Ascending') */ sortAscText : 'Sort Ascending',
    /** * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to 'Sort Descending') */ sortDescText : 'Sort Descending', constructor : function(tree, config) { if(!Ext.isObject(config)) { config = { property: tree.columns[0].dataIndex || 'text', folderSort: true } } Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments); this.tree = tree; tree.on('headerclick', this.onHeaderClick, this); tree.ddAppendOnly = true; var me = this; this.defaultSortFn = function(n1, n2){ var desc = me.dir && me.dir.toLowerCase() == 'desc', prop = me.property || 'text', sortType = me.sortType, caseSensitive = me.caseSensitive === true, leafAttr = me.leafAttr || 'leaf', attr1 = n1.attributes, attr2 = n2.attributes; if(me.folderSort){ if(attr1[leafAttr] && !attr2[leafAttr]){ return 1; } if(!attr1[leafAttr] && attr2[leafAttr]){ return -1; } } var prop1 = attr1[prop], prop2 = attr2[prop], v1 = sortType ? sortType(prop1) : (caseSensitive ? prop1 : prop1.toUpperCase()); v2 = sortType ? sortType(prop2) : (caseSensitive ? prop2 : prop2.toUpperCase()); if(v1 < v2){ return desc ? +1 : -1; }else if(v1 > v2){ return desc ? -1 : +1; }else{ return 0; } }; tree.on('afterrender', this.onAfterTreeRender, this, {single: true}); tree.on('headermenuclick', this.onHeaderMenuClick, this); }, onAfterTreeRender : function() { if(this.tree.hmenu){ this.tree.hmenu.insert(0, {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'}, {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'} ); } this.updateSortIcon(0, 'asc'); }, onHeaderMenuClick : function(c, id, index) { if(id === 'asc' || id === 'desc') { this.onHeaderClick(c, null, index); return false; } }, onHeaderClick : function(c, el, i) { if(c && !this.tree.headersDisabled){ var me = this; me.property = c.dataIndex; me.dir = c.dir = (c.dir === 'desc' ? 'asc' : 'desc'); me.sortType = c.sortType; me.caseSensitive === Ext.isBoolean(c.caseSensitive) ? c.caseSensitive : this.caseSensitive; me.sortFn = c.sortFn || this.defaultSortFn; this.tree.root.cascade(function(n) { if(!n.isLeaf()) { me.updateSort(me.tree, n); } }); this.updateSortIcon(i, c.dir); } }, // private updateSortIcon : function(col, dir){ var sc = this.sortClasses, hds = this.tree.innerHd.select('td').removeClass(sc); hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]); } });/** * @class Ext.ux.tree.TreeGridLoader * @extends Ext.tree.TreeLoader */ Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, { createNode : function(attr) { if (!attr.uiProvider) { attr.uiProvider = Ext.ux.tree.TreeGridNodeUI; } return Ext.tree.TreeLoader.prototype.createNode.call(this, attr); } });/** * @class Ext.ux.tree.TreeGrid * @extends Ext.tree.TreePanel * * @xtype treegrid */ Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, { rootVisible : false, useArrows : true, lines : false, borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell cls : 'x-treegrid', columnResize : true, enableSort : true, reserveScrollOffset : true, enableHdMenu : true, columnsText : 'Columns', initComponent : function() { if(!this.root) { this.root = new Ext.tree.AsyncTreeNode({text: 'Root'}); } // initialize the loader var l = this.loader; if(!l){ l = new Ext.ux.tree.TreeGridLoader({ dataUrl: this.dataUrl, requestMethod: this.requestMethod, store: this.store }); }else if(Ext.isObject(l) && !l.load){ l = new Ext.ux.tree.TreeGridLoader(l); } this.loader = l; Ext.ux.tree.TreeGrid.superclass.initComponent.call(this); this.initColumns(); if(this.enableSort) { this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort); } if(this.columnResize){ this.colResizer = new Ext.tree.ColumnResizer(this.columnResize); this.colResizer.init(this); } var c = this.columns; if(!this.internalTpl){ this.internalTpl = new Ext.XTemplate( '
    ', '
    ', '
    ', '', '', '', '', '', '
    ', '
    ', this.enableHdMenu ? '' : '', '{header}', '
    ', '
    ', '
    ', '
    ', '
    ', '
    ', '
    ' ); } if(!this.colgroupTpl) { this.colgroupTpl = new Ext.XTemplate( '' ); } }, initColumns : function() { var cs = this.columns, len = cs.length, columns = [], i, c; for(i = 0; i < len; i++){ c = cs[i]; if(!c.isColumn) { c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn'; c = Ext.create(c); } c.init(this); columns.push(c); if(this.enableSort !== false && c.sortable !== false) { c.sortable = true; this.enableSort = true; } } this.columns = columns; }, onRender : function(){ Ext.tree.TreePanel.superclass.onRender.apply(this, arguments); this.el.addClass('x-treegrid'); this.outerCt = this.body.createChild({ cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines') }); this.internalTpl.overwrite(this.outerCt, {columns: this.columns}); this.mainHd = Ext.get(this.outerCt.dom.firstChild); this.innerHd = Ext.get(this.mainHd.dom.firstChild); this.innerBody = Ext.get(this.outerCt.dom.lastChild); this.innerCt = Ext.get(this.innerBody.dom.firstChild); this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns}); if(this.hideHeaders){ this.el.child('.x-grid3-header').setDisplayed('none'); } else if(this.enableHdMenu !== false){ this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'}); if(this.enableColumnHide !== false){ this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'}); this.colMenu.on({ scope: this, beforeshow: this.beforeColMenuShow, itemclick: this.handleHdMenuClick }); this.hmenu.add({ itemId:'columns', hideOnClick: false, text: this.columnsText, menu: this.colMenu, iconCls: 'x-cols-icon' }); } this.hmenu.on('itemclick', this.handleHdMenuClick, this); } }, setRootNode : function(node){ node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI; node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node); if(this.innerCt) { this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns}); } return node; }, clearInnerCt : function(){ if(Ext.isIE){ var dom = this.innerCt.dom; while(dom.firstChild){ dom.removeChild(dom.firstChild); } }else{ Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this); } }, initEvents : function() { Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments); this.mon(this.innerBody, 'scroll', this.syncScroll, this); this.mon(this.innerHd, 'click', this.handleHdDown, this); this.mon(this.mainHd, { scope: this, mouseover: this.handleHdOver, mouseout: this.handleHdOut }); }, onResize : function(w, h) { Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments); var bd = this.innerBody.dom; var hd = this.innerHd.dom; if(!bd){ return; } if(Ext.isNumber(h)){ bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px'; } if(Ext.isNumber(w)){ var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth()); if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){ this.setScrollOffset(sw); }else{ var me = this; setTimeout(function(){ me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0); }, 10); } } }, updateColumnWidths : function() { var cols = this.columns, colCount = cols.length, groups = this.outerCt.query('colgroup'), groupCount = groups.length, c, g, i, j; for(i = 0; i 0 && this.columns[index]) { this.setColumnVisible(index, !item.checked); } } return true; }, setColumnVisible : function(index, visible) { this.columns[index].hidden = !visible; this.updateColumnWidths(); },
    /** * Scrolls the grid to the top */ scrollToTop : function(){ this.innerBody.dom.scrollTop = 0; this.innerBody.dom.scrollLeft = 0; }, // private syncScroll : function(){ this.syncHeaderScroll(); var mb = this.innerBody.dom; this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop); }, // private syncHeaderScroll : function(){ var mb = this.innerBody.dom; this.innerHd.dom.scrollLeft = mb.scrollLeft; this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore) }, registerNode : function(n) { Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n); if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) { n.ui = new Ext.ux.tree.TreeGridNodeUI(n); } } }); Ext.reg('treegrid', Ext.ux.tree.TreeGrid);