X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..b37ceabb82336ee82757cd32efe353cfab8ec267:/examples/ux/ux-all-debug.js?ds=sidebyside diff --git a/examples/ux/ux-all-debug.js b/examples/ux/ux-all-debug.js index 6ef28d4d..49e48952 100644 --- a/examples/ux/ux-all-debug.js +++ b/examples/ux/ux-all-debug.js @@ -1,6 +1,6 @@ /*! - * Ext JS Library 3.0.0 - * Copyright(c) 2006-2009 Ext JS, LLC + * Ext JS Library 3.2.2 + * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ @@ -75,15 +75,15 @@ Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, { }, getVisibleRowCount : function(){ - var rh = this.getCalculatedRowHeight(); - var visibleHeight = this.scroller.dom.clientHeight; + var rh = this.getCalculatedRowHeight(), + visibleHeight = this.scroller.dom.clientHeight; return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh); }, getVisibleRows: function(){ - var count = this.getVisibleRowCount(); - var sc = this.scroller.dom.scrollTop; - var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-1); + 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) @@ -91,25 +91,34 @@ Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, { }, doRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){ - var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1; - var rh = this.getStyleRowHeight(); - var vr = this.getVisibleRows(); - var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;'; - // buffers - var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r; + 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); - var visible = rowIndex >= vr.first && rowIndex <= vr.last; + 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.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 === "") { + if (p.value === undefined || p.value === "") { p.value = " "; } if (r.dirty && typeof r.modified[c.name] !== 'undefined') { @@ -119,7 +128,7 @@ Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, { } } var alt = []; - if(stripe && ((rowIndex+1) % 2 == 0)){ + if(stripe && ((rowIndex+1) % 2 === 0)){ alt[0] = "x-grid3-row-alt"; } if(r.dirty){ @@ -157,18 +166,27 @@ Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, { 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; - var cs = this.getColumnData(); - - var vr = this.getVisibleRows(); + 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)){ + if(!this.isRowRendered(i) && (row = this.getRow(i))){ var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true); - this.getRow(i).innerHTML = html; + row.innerHTML = html; } } this.clean(); @@ -204,13 +222,26 @@ Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, { } } }, + + 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 +});// 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'); @@ -266,332 +297,811 @@ Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, { }); Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout; -Ext.ns('Ext.ux.grid'); - -/** - * @class Ext.ux.grid.CheckColumn - * @extends Object - * GridPanel plugin to add a column with check boxes to a grid. - *
Example usage:
- *
-// create the column
-var checkColumn = new Ext.grid.CheckColumn({
- header: 'Indoor?',
- dataIndex: 'indoor',
- id: 'check',
- width: 55
-});
-
-// add the column to the column model
-var cm = new Ext.grid.ColumnModel([{
- header: 'Foo',
- ...
- },
- checkColumn
-]);
-
-// create the grid
-var grid = new Ext.grid.EditorGridPanel({
- ...
- cm: cm,
- plugins: [checkColumn], // include plugin
- ...
-});
- *
- * In addition to storing a Boolean value within the record data, this
- * class toggles a css class between 'x-grid3-check-col' and
- * 'x-grid3-check-col-on' to alter the background image used for
- * a column.
- */
-Ext.ux.grid.CheckColumn = function(config){
- Ext.apply(this, config);
- if(!this.id){
- this.id = Ext.id();
- }
- this.renderer = this.renderer.createDelegate(this);
-};
-
-Ext.ux.grid.CheckColumn.prototype ={
- init : function(grid){
- this.grid = grid;
- this.grid.on('render', function(){
- var view = this.grid.getView();
- view.mainBody.on('mousedown', this.onMouseDown, this);
- }, this);
- },
-
- onMouseDown : function(e, t){
- if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){
- e.stopEvent();
- var index = this.grid.getView().findRowIndex(t);
- var record = this.grid.store.getAt(index);
- record.set(this.dataIndex, !record.data[this.dataIndex]);
- }
- },
-
- renderer : function(v, p, record){
- p.css += ' x-grid3-check-col-td';
- return 'Example usage:
+ *
+// create the column
+var checkColumn = new Ext.grid.CheckColumn({
+ header: 'Indoor?',
+ dataIndex: 'indoor',
+ id: 'check',
+ width: 55
+});
+
+// add the column to the column model
+var cm = new Ext.grid.ColumnModel([{
+ header: 'Foo',
+ ...
+ },
+ checkColumn
+]);
+
+// create the grid
+var grid = new Ext.grid.EditorGridPanel({
+ ...
+ cm: cm,
+ plugins: [checkColumn], // include plugin
+ ...
+});
+ *
+ * In addition to storing a Boolean value within the record data, this
+ * class toggles a css class between 'x-grid3-check-col' and
+ * 'x-grid3-check-col-on' to alter the background image used for
+ * a column.
+ */
+Ext.ux.grid.CheckColumn = function(config){
+ Ext.apply(this, config);
+ if(!this.id){
+ this.id = Ext.id();
+ }
+ this.renderer = this.renderer.createDelegate(this);
+};
+
+Ext.ux.grid.CheckColumn.prototype ={
+ init : function(grid){
+ this.grid = grid;
+ this.grid.on('render', function(){
+ var view = this.grid.getView();
+ view.mainBody.on('mousedown', this.onMouseDown, this);
+ }, this);
+ },
+
+ onMouseDown : function(e, t){
+ if(Ext.fly(t).hasClass(this.createId())){
+ e.stopEvent();
+ var index = this.grid.getView().findRowIndex(t);
+ var record = this.grid.store.getAt(index);
+ record.set(this.dataIndex, !record.data[this.dataIndex]);
+ }
+ },
+
+ renderer : function(v, p, record){
+ p.css += ' x-grid3-check-col-td';
+ return String.format('
-summaryRenderer: function(v, params, data){
- return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
-},
- *
- */
- /**
- * @cfg {String} summaryType (Optional) The type of
- * calculation to be used for the column. For options available see
- * {@link #Calculations}.
- */
-
- constructor : function(config){
- Ext.apply(this, config);
- Ext.ux.grid.GroupSummary.superclass.constructor.call(this);
- },
- init : function(grid){
- this.grid = grid;
- this.cm = grid.getColumnModel();
- this.view = grid.getView();
-
- var v = this.view;
- v.doGroupEnd = this.doGroupEnd.createDelegate(this);
-
- v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
- v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
- v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);
- v.afterMethod('onUpdate', this.doUpdate, this);
- v.afterMethod('onRemove', this.doRemove, this);
-
- if(!this.rowTpl){
- this.rowTpl = new Ext.Template(
- '
-grid.on('afteredit', function(){
- var groupValue = 'Ext Forms: Field Anchoring';
- summary.showSummaryMsg(groupValue, 'Updating Summary...');
-});
- *
- * @param {String} groupValue
- * @param {String} msg Text to use as innerHTML for the summary row.
- */
- showSummaryMsg : function(groupValue, msg){
- var gid = this.view.getGroupId(groupValue);
- var node = this.getSummaryNode(gid);
- if(node){
- node.innerHTML = 'Custom calculations may be implemented. An example of
- * custom summaryType=totalCost
:
-// define a custom summary function
-Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
- return v + (record.data.estimate * record.data.rate);
-};
- *
- * @property Calculations
- */
-
-Ext.ux.grid.GroupSummary.Calculations = {
- 'sum' : function(v, record, field){
- return v + (record.data[field]||0);
- },
-
- 'count' : function(v, record, field, data){
- return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
- },
-
- 'max' : function(v, record, field, data){
- var v = record.data[field];
- var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];
- return v > max ? (data[field+'max'] = v) : max;
- },
-
- 'min' : function(v, record, field, data){
- var v = record.data[field];
- var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];
- return v < min ? (data[field+'min'] = v) : min;
- },
-
- 'average' : function(v, record, field, data){
- var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
- var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));
- return t === 0 ? 0 : t / c;
- }
-};
-Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;
-
-/**
- * @class Ext.ux.grid.HybridSummary
- * @extends Ext.ux.grid.GroupSummary
- * Adds capability to specify the summary data for the group via json as illustrated here:
- *
-{
- data: [
- {
- projectId: 100, project: 'House',
- taskId: 112, description: 'Paint',
- estimate: 6, rate: 150,
- due:'06/24/2007'
- },
- ...
- ],
-
- summaryData: {
- 'House': {
- description: 14, estimate: 9,
- rate: 99, due: new Date(2009, 6, 29),
- cost: 999
- }
- }
-}
- *
- *
- */
-Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {
- /**
- * @private
- * @param {Object} rs
- * @param {Object} cs
- */
- calculate : function(rs, cs){
- var gcol = this.view.getGroupField();
- var gvalue = rs[0].data[gcol];
- var gdata = this.getSummaryData(gvalue);
- return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);
- },
-
- /**
- *
-grid.on('afteredit', function(){
- var groupValue = 'Ext Forms: Field Anchoring';
- summary.showSummaryMsg(groupValue, 'Updating Summary...');
- setTimeout(function(){ // simulate server call
- // HybridSummary class implements updateSummaryData
- summary.updateSummaryData(groupValue,
- // create data object based on configured dataIndex
- {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});
- }, 2000);
-});
- *
- * @param {String} groupValue
- * @param {Object} data data object
- * @param {Boolean} skipRefresh (Optional) Defaults to false
- */
- updateSummaryData : function(groupValue, data, skipRefresh){
- var json = this.grid.store.reader.jsonData;
- if(!json.summaryData){
- json.summaryData = {};
- }
- json.summaryData[groupValue] = data;
- if(!skipRefresh){
- this.refreshSummary(groupValue);
- }
- },
-
- /**
- * Returns the summaryData for the specified groupValue or null.
- * @param {String} groupValue
- * @return {Object} summaryData
- */
- getSummaryData : function(groupValue){
- var json = this.grid.store.reader.jsonData;
- if(json && json.summaryData){
- return json.summaryData[groupValue];
- }
- return null;
- }
-});
-
-//backwards compat
-Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;
-Ext.ux.GroupTab = Ext.extend(Ext.Container, {
- mainItem: 0,
-
- expanded: true,
-
- deferredRender: true,
-
- activeTab: null,
-
- idDelimiter: '__',
-
- headerAsText: false,
-
- frame: false,
-
- hideBorders: true,
-
- initComponent: function(config){
- Ext.apply(this, config);
- this.frame = false;
-
- Ext.ux.GroupTab.superclass.initComponent.call(this);
-
- this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');
-
- this.setLayout(new Ext.layout.CardLayout({
- deferredRender: this.deferredRender
- }));
-
- if (!this.stack) {
- this.stack = Ext.TabPanel.AccessStack();
- }
-
- this.initItems();
-
- this.on('beforerender', function(){
- this.groupEl = this.ownerCt.getGroupEl(this);
- }, this);
-
- this.on('add', this.onAdd, this, {
- target: this
- });
- this.on('remove', this.onRemove, this, {
- target: this
- });
-
- if (this.mainItem !== undefined) {
- var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);
- delete this.mainItem;
- this.setMainItem(item);
- }
- },
-
- /**
- * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which
- * can return false to cancel the tab change.
- * @param {String/Panel} tab The id or tab Panel to activate
- */
- setActiveTab : function(item){
- item = this.getComponent(item);
- if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){
- return;
- }
- if(!this.rendered){
- this.activeTab = item;
- return;
- }
- if(this.activeTab != item){
- if(this.activeTab && this.activeTab != this.mainItem){
- var oldEl = this.getTabEl(this.activeTab);
- if(oldEl){
- Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');
- }
- this.activeTab.fireEvent('deactivate', this.activeTab);
- }
- var el = this.getTabEl(item);
- Ext.fly(el).addClass('x-grouptabs-strip-active');
- this.activeTab = item;
- this.stack.add(item);
-
- this.layout.setActiveItem(item);
- if(this.layoutOnTabChange && item.doLayout){
- item.doLayout();
- }
- if(this.scrolling){
- this.scrollToTab(item, this.animScroll);
- }
-
- item.fireEvent('activate', item);
- this.fireEvent('tabchange', this, item);
- }
- },
-
- getTabEl: function(item){
- if (item == this.mainItem) {
- return this.groupEl;
- }
- return Ext.TabPanel.prototype.getTabEl.call(this, item);
- },
-
- onRender: function(ct, position){
- Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);
-
- this.strip = Ext.fly(this.groupEl).createChild({
- tag: 'ul',
- cls: 'x-grouptabs-sub'
- });
-
- this.tooltip = new Ext.ToolTip({
- target: this.groupEl,
- delegate: 'a.x-grouptabs-text',
- trackMouse: true,
- renderTo: document.body,
- listeners: {
- beforeshow: function(tip) {
- var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)
- ? this.mainItem
- : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);
-
- if(!item.tabTip) {
- return false;
- }
- tip.body.dom.innerHTML = item.tabTip;
- },
- scope: this
- }
- });
-
- if (!this.itemTpl) {
- var tt = new Ext.Template('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:
+ *stateId
in the Grid configuration.
+ * {@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}
+ * and {@link Ext.grid.GridPanel#beforestatesave beforestatesave}
+ * events in order to be stateful.
+ * filters
property is added to the grid pointing to
+ * this plugin.filterupdate
event is added to the grid and is
+ * fired upon onStateChange completion.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:
+ * {@link #paramPrefix}='filters'
:
+ *
+filters[0][field]="someDataIndex"&
+filters[0][data][comparison]="someValue1"&
+filters[0][data][type]="someValue2"&
+filters[0][data][value]="someValue3"&
+ *
+ *
+filters[0][field]="someDataIndex"&
+filters[0][data][comparison]="someValue1"&
+filters[0][data][type]="someValue2"&
+filters[0][data][value]="someValue3"&
+ *
+ * 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
+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:
+ *
+options: [
+ [11, 'extra small'],
+ [18, 'small'],
+ [22, 'medium'],
+ [35, 'large'],
+ [44, 'extra large']
+]
+ *
+ *
+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'}
+]
+ *
+ *
+ * options: ['extra small', 'small', 'medium', 'large', 'extra large']
+ *
+ * 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; iExample 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 JS Library 3.2.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+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(
+ '
+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 = '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('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 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();
}
- }
- return res;
- };
-}
-Ext.ns('Ext.ux.data');
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
-/**
- * @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; + 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); }, - 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); + + // 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; } - - // 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; + + 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); + } } - - // sorting - if (params.sort !== undefined) { - // use integer as params.sort to specify column, since arrays are not named - // params.sort=0; would also match a array without columns - var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1; - var fn = function(r1, r2){ - return r1 < r2; - }; - result.records.sort(function(a, b){ - var v = 0; - if (typeof(a) == 'object') { - v = fn(a.data[params.sort], b.data[params.sort]) * dir; - } - else { - v = fn(a, b) * dir; - } - if (v == 0) { - v = (a.index < b.index ? -1 : 1); - } - return v; - }); + + 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}); + } } - // 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); + + // 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); + } + } } - callback.call(scope, result, options, true); + delete this.adjustmentPass; } + + /** + * @property activeItem + * @hide + */ }); -//backwards compat. -Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy; -Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, { - minHeight: 0, - maxHeight:10000000, - - constructor: function(config){ - Ext.apply(this, config); - this.events = {}; - Ext.ux.PanelResizer.superclass.constructor.call(this, config); - }, - - init : function(p){ - this.panel = p; - - if(this.panel.elements.indexOf('footer')==-1){ - p.elements += ',footer'; - } - p.on('render', this.onRender, this); - }, - - onRender : function(p){ - this.handle = p.footer.createChild({cls:'x-panel-resize'}); - - this.tracker = new Ext.dd.DragTracker({ - onStart: this.onDragStart.createDelegate(this), - onDrag: this.onDrag.createDelegate(this), - onEnd: this.onDragEnd.createDelegate(this), - tolerance: 3, - autoStart: 300 - }); - this.tracker.initEl(this.handle); - p.on('beforedestroy', this.tracker.destroy, this.tracker); - }, - - // private - onDragStart: function(e){ - this.dragging = true; - this.startHeight = this.panel.el.getHeight(); - this.fireEvent('dragstart', this, e); - }, - - // private - onDrag: function(e){ - this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight)); - this.fireEvent('drag', this, e); - }, - - // private - onDragEnd: function(e){ - this.dragging = false; - this.fireEvent('dragend', this, e); - } -}); -Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, { - layout : 'column', - autoScroll : true, - cls : 'x-portal', - defaultType : 'portalcolumn', - - initComponent : function(){ - Ext.ux.Portal.superclass.initComponent.call(this); - this.addEvents({ - validatedrop:true, - beforedragover:true, - dragover:true, - beforedrop:true, - drop:true - }); - }, - - initEvents : function(){ - Ext.ux.Portal.superclass.initEvents.call(this); - this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig); - }, - - beforeDestroy : function() { - if(this.dd){ - this.dd.unreg(); - } - Ext.ux.Portal.superclass.beforeDestroy.call(this); - } -}); - -Ext.reg('portal', Ext.ux.Portal); - - -Ext.ux.Portal.DropZone = function(portal, cfg){ - this.portal = portal; - Ext.dd.ScrollManager.register(portal.body); - Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg); - portal.body.ddScrollConfig = this.ddScrollConfig; -}; - -Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, { - ddScrollConfig : { - vthresh: 50, - hthresh: -1, - animate: true, - increment: 200 - }, - - createEvent : function(dd, e, data, col, c, pos){ - return { - portal: this.portal, - panel: data.panel, - columnIndex: col, - column: c, - position: pos, - data: data, - source: dd, - rawEvent: e, - status: this.dropAllowed - }; - }, - - notifyOver : function(dd, e, data){ - var xy = e.getXY(), portal = this.portal, px = dd.proxy; - - // case column widths - if(!this.grid){ - this.grid = this.getGrid(); - } - - // handle case scroll where scrollbars appear during drag - var cw = portal.body.dom.clientWidth; - if(!this.lastCW){ - this.lastCW = cw; - }else if(this.lastCW != cw){ - this.lastCW = cw; - portal.doLayout(); - this.grid = this.getGrid(); - } - - // determine column - var col = 0, xs = this.grid.columnX, cmatch = false; - for(var len = xs.length; col < len; col++){ - if(xy[0] < (xs[col].x + xs[col].w)){ - cmatch = true; - break; - } - } - // no match, fix last index - if(!cmatch){ - col--; - } - - // find insert position - var p, match = false, pos = 0, - c = portal.items.itemAt(col), - items = c.items.items, overSelf = false; - - for(var len = items.length; pos < len; pos++){ - p = items[pos]; - var h = p.el.getHeight(); - if(h === 0){ - overSelf = true; - } - else if((p.el.getY()+(h/2)) > xy[1]){ - match = true; - break; - } - } - - pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0); - var overEvent = this.createEvent(dd, e, data, col, c, pos); - - if(portal.fireEvent('validatedrop', overEvent) !== false && - portal.fireEvent('beforedragover', overEvent) !== false){ - - // make sure proxy width is fluid - px.getProxy().setWidth('auto'); - - if(p){ - px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null); - }else{ - px.moveProxy(c.el.dom, null); - } - - this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false}; - this.scrollPos = portal.body.getScroll(); - - portal.fireEvent('dragover', overEvent); - - return overEvent.status; - }else{ - return overEvent.status; - } - - }, - - notifyOut : function(){ - delete this.grid; - }, - - notifyDrop : function(dd, e, data){ - delete this.grid; - if(!this.lastPos){ - return; - } - var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p; - - var dropEvent = this.createEvent(dd, e, data, col, c, - pos !== false ? pos : c.items.getCount()); - - if(this.portal.fireEvent('validatedrop', dropEvent) !== false && - this.portal.fireEvent('beforedrop', dropEvent) !== false){ - - dd.proxy.getProxy().remove(); - dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom); - - if(pos !== false){ - if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){ - pos++; - } - c.insert(pos, dd.panel); - }else{ - c.add(dd.panel); - } - - c.doLayout(); - - this.portal.fireEvent('drop', dropEvent); - - // scroll position is lost on drop, fix it - var st = this.scrollPos.top; - if(st){ - var d = this.portal.body.dom; - setTimeout(function(){ - d.scrollTop = st; - }, 10); - } - - } - delete this.lastPos; - }, - - // internal cache of body and column coords - getGrid : function(){ - var box = this.portal.bwrap.getBox(); - box.columnX = []; - this.portal.items.each(function(c){ - box.columnX.push({x: c.el.getX(), w: c.el.getWidth()}); - }); - return box; - }, - - // unregister the dropzone from ScrollManager - unreg: function() { - //Ext.dd.ScrollManager.unregister(this.portal.body); - Ext.ux.Portal.DropZone.superclass.unreg.call(this); - } -}); -Ext.ux.PortalColumn = Ext.extend(Ext.Container, { - layout : 'anchor', - //autoEl : 'div',//already defined by Ext.Component - defaultType : 'portlet', - cls : 'x-portal-column' -}); - -Ext.reg('portalcolumn', Ext.ux.PortalColumn); -Ext.ux.Portlet = Ext.extend(Ext.Panel, { - anchor : '100%', - frame : true, - collapsible : true, - draggable : true, - cls : 'x-portlet' -}); - -Ext.reg('portlet', Ext.ux.Portlet); +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.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); + * @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); }, - //public - init : function (parent) { - - if(parent.displayInfo){ - this.parent = parent; - var ind = parent.items.indexOf(parent.displayItem); - parent.remove(parent.displayItem, true); - this.progressBar = new Ext.ProgressBar({ - text : this.defaultText, - width : this.progBarWidth, - animate : this.defaultAnimCfg - }); - - parent.displayItem = this.progressBar; - - parent.add(parent.displayItem); - parent.doLayout(); - Ext.apply(parent, this.parentOverrides); - - this.progressBar.on('render', function(pb) { - pb.el.applyStyles('cursor:pointer'); - pb.el.on('click', this.handleProgressBarClick, this); - }, this); - - - // Remove the click handler from the - this.progressBar.on({ - scope : this, - beforeDestroy : function() { - this.progressBar.el.un('click', this.handleProgressBarClick, this); - } - }); - + 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(); } - }, - // private - // This method handles the click for the progress bar - handleProgressBarClick : function(e){ - var parent = this.parent; - var displayItem = parent.displayItem; - - var box = this.progressBar.getBox(); - var xy = e.getXY(); - var position = xy[0]-box.x; - var pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize); - - var newpage = Math.ceil(position/(displayItem.width/pages)); - parent.changePage(newpage); + + 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); + } }, - - // private, overriddes - parentOverrides : { - // private - // This method updates the information via the progress bar. - updateInfo : function(){ - if(this.displayItem){ - var count = this.store.getCount(); - var pgData = this.getPageData(); - var pageNum = this.readPage(pgData); - - var msg = count == 0 ? - this.emptyMsg : - String.format( - this.displayMsg, - this.cursor+1, this.cursor+count, this.store.getTotalCount() - ); - - pageNum = pgData.activePage; ; - - var pct = pageNum / pgData.pages; - - this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig); + + 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.preg('progressbarpager', Ext.ux.ProgressBarPager); -Ext.ns('Ext.ux.grid'); +Ext.reg('selectbox', Ext.ux.form.SelectBox); +//backwards compat +Ext.ux.SelectBox = Ext.ux.form.SelectBox; /** - * @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 + * Plugin for PagingToolbar which replaces the textfield input with a slider */ -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, +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'); - defaults: { - normalWidth: true +/** + * @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; }, - initComponent: function(){ - Ext.ux.grid.RowEditor.superclass.initComponent.call(this); - this.addEvents( - /** - * @event beforeedit - * Fired before the row editor is activated. - * If the listener returns false the editor will not be activated. - * @param {Ext.ux.grid.RowEditor} roweditor This object - * @param {Number} rowIndex The rowIndex of the row just edited - */ - 'beforeedit', - /** - * @event validateedit - * Fired after a row is edited and passes validation. - * If the listener returns false changes to the record will not be set. - * @param {Ext.ux.grid.RowEditor} roweditor This object - * @param {Object} changes Object with changes made to the record. - * @param {Ext.data.Record} r The Record that was edited. - * @param {Number} rowIndex The rowIndex of the row just edited - */ - 'validateedit', - /** - * @event afteredit - * Fired after a row is edited and passes validation. This event is fired - * after the store's update event is fired with this edit. - * @param {Ext.ux.grid.RowEditor} roweditor This object - * @param {Object} changes Object with changes made to the record. - * @param {Ext.data.Record} r The Record that was edited. - * @param {Number} rowIndex The rowIndex of the row just edited - */ - 'afteredit' - ); + // private + getPositionEl: function(){ + return this.wrap; }, - 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); - } + // private + alignErrorIcon: function(){ + if (this.wrap) { + this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); } + }, - // stopEditing without saving when a record is removed from Store. - grid.getStore().on('remove', function() { - this.stopEditing(false); - },this); + validateBlur: function(){ + return true; + } +}); - grid.on({ - scope: this, - keydown: this.onGridKey, - columnresize: this.verifyLayout, - columnmove: this.refreshFields, - reconfigure: this.refreshFields, - destroy : this.destroy, - bodyscroll: { - buffer: 250, - fn: this.positionButtons - } - }); - grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1}); - grid.getView().on('refresh', this.stopEditing.createDelegate(this, [])); +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; }, - refreshFields: function(){ - this.initFields(); - this.verifyLayout(); + 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); }, - 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; + 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(); }, - startEditing: function(rowIndex, doFocus){ - if(this.editing && this.isDirty()){ - this.showTooltip('You need to commit or cancel your changes'); - return; + doAfterRender: function(){ + var y; + if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) { + this.el.position(); + this.el.setY(y); } - this.editing = true; - if(typeof rowIndex == 'object'){ - rowIndex = this.grid.getStore().indexOf(rowIndex); + }, + + doEnable: function(){ + if (this.wrap) { + this.wrap.removeClass(this.field.disabledClass); } - if(this.fireEvent('beforeedit', this, rowIndex) !== false){ - var g = this.grid, view = g.getView(); - var row = view.getRow(rowIndex); - var record = g.store.getAt(rowIndex); - this.record = record; - this.rowIndex = rowIndex; - this.values = {}; - if(!this.rendered){ - this.render(view.getEditorParent()); - } - var w = Ext.fly(row).getWidth(); - this.setSize(w); - if(!this.initialized){ - this.initFields(); - } - var cm = g.getColumnModel(), fields = this.items.items, f, val; - for(var i = 0, len = cm.getColumnCount(); i < len; i++){ - val = this.preEditValue(record, cm.getDataIndex(i)); - f = fields[i]; - f.setValue(val); - this.values[f.id] = val || ''; - } - this.verifyLayout(true); - if(!this.isVisible()){ - this.setPagePosition(Ext.fly(row).getXY()); - } else{ - this.el.setXY(Ext.fly(row).getXY(), {duration:0.15}); - } - if(!this.isVisible()){ - this.show().doLayout(); - } - if(doFocus !== false){ - this.doFocus.defer(this.focusDelay, this); - } + }, + + doDisable: function(){ + if (this.wrap) { + this.wrap.addClass(this.field.disabledClass); + this.el.removeClass(this.field.disabledClass); } }, - stopEditing : function(saveChanges){ - this.editing = false; - if(!this.isVisible()){ - return; + doResize: function(w, h){ + if (typeof w == 'number') { + this.el.setWidth(w - this.trigger.getWidth()); } - if(saveChanges === false || !this.isValid()){ - this.hide(); - return; + 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); } - var changes = {}, r = this.record, hasChange = false; - var cm = this.grid.colModel, fields = this.items.items; - for(var i = 0, len = cm.getColumnCount(); i < len; i++){ - if(!cm.isHidden(i)){ - var dindex = cm.getDataIndex(i); - if(!Ext.isEmpty(dindex)){ - var oldValue = r.data[dindex]; - var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex); - if(String(oldValue) !== String(value)){ - changes[dindex] = value; - hasChange = true; - } - } - } + }, + + // private + checkTab: function(e){ + if (e.getKey() == e.TAB) { + this.triggerBlur(); } - if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){ - r.beginEdit(); - for(var k in changes){ - if(changes.hasOwnProperty(k)){ - r.set(k, changes[k]); - } - } - r.endEdit(); - this.fireEvent('afteredit', this, changes, r, this.rowIndex); + }, + + // private + mimicBlur: function(e){ + if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) { + this.triggerBlur(); } - this.hide(); }, - verifyLayout: function(force){ - if(this.el && (this.isVisible() || force === true)){ - var row = this.grid.getView().getRow(this.rowIndex); - this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + (Ext.isBorderBox ? 9 : 0) : undefined); - var cm = this.grid.colModel, fields = this.items.items; - for(var i = 0, len = cm.getColumnCount(); i < len; i++){ - if(!cm.isHidden(i)){ - var adjust = 0; - if(i === 0){ - adjust += 0; // outer padding - } - if(i === (len - 1)){ - adjust += 3; // outer padding - } else{ - adjust += 1; - } - fields[i].show(); - fields[i].setWidth(cm.getColumnWidth(i) - adjust); - } else{ - fields[i].hide(); - } - } - this.doLayout(); - this.positionButtons(); + // 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); }, - slideHide : function(){ - this.hide(); + //private + onMouseOut: function(){ + this.trigger.removeClass(this.tmpHoverClass); }, - initFields: function(){ - var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins; - this.removeAll(false); - for(var i = 0, len = cm.getColumnCount(); i < len; i++){ - var c = cm.getColumnAt(i); - var ed = c.getEditor(); - if(!ed){ - ed = c.displayEditor || new Ext.form.DisplayField(); - } - if(i == 0){ - ed.margins = pm('0 1 2 1'); - } else if(i == len - 1){ - ed.margins = pm('0 0 2 1'); - } else{ - ed.margins = pm('0 1 2'); - } - ed.setWidth(cm.getColumnWidth(i)); - ed.column = c; - if(ed.ownerCt !== this){ - ed.on('focus', this.ensureVisible, this); - ed.on('specialkey', this.onKey, this); - } - this.insert(i, ed); + //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")) { } - this.initialized = true; }, - onKey: function(f, e){ - if(e.getKey() === e.ENTER){ - this.stopEditing(true); - e.stopPropagation(); + //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); }, - 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(); - } + //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](); }, - ensureVisible: function(editor){ - if(this.isVisible()){ - this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true); + //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; }, - onRowClick: function(g, rowIndex, e){ - if(this.clicksToEdit == 'auto'){ - var li = this.lastClickIndex; - this.lastClickIndex = rowIndex; - if(li != rowIndex && !this.isVisible()){ - return; - } + handleMouseWheel: function(e){ + //disable scrolling when not focused + if (this.wrap.hasClass('x-trigger-wrap-focus') == false) { + return; } - this.startEditing(rowIndex, false); - this.doFocus.defer(this.focusDelay, this, [e.getPoint()]); + + var delta = e.getWheelDelta(); + if (delta > 0) { + this.onSpinUp(); + e.stopEvent(); + } + else + if (delta < 0) { + this.onSpinDown(); + e.stopEvent(); + } }, - onRowDblClick: function(g, rowIndex, e){ - this.startEditing(rowIndex, false); - this.doFocus.defer(this.focusDelay, this, [e.getPoint()]); + //private + startDrag: function(){ + this.proxy.show(); + this._previousY = Ext.fly(this.dd.getDragEl()).getTop(); }, - onRender: function(){ - Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments); - this.el.swallowEvent(['keydown', 'keyup', 'keypress']); - this.btns = new Ext.Panel({ - baseCls: 'x-plain', - cls: 'x-btns', - elements:'body', - layout: 'table', - width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE - items: [{ - ref: 'saveBtn', - itemId: 'saveBtn', - xtype: 'button', - text: this.saveText || 'Save', - width: this.minButtonWidth, - handler: this.stopEditing.createDelegate(this, [true]) - }, { - xtype: 'button', - text: this.cancelText || 'Cancel', - width: this.minButtonWidth, - handler: this.stopEditing.createDelegate(this, [false]) - }] - }); - this.btns.render(this.bwrap); + //private + endDrag: function(){ + this.proxy.hide(); }, - afterRender: function(){ - Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments); - this.positionButtons(); - if(this.monitorValid){ - this.startMonitoring(); + //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; }, - onShow: function(){ - if(this.monitorValid){ - this.startMonitoring(); + //private + onSpinUp: function(){ + if (this.isSpinnable() == false) { + return; } - Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments); + if (Ext.EventObject.shiftKey == true) { + this.onSpinUpAlternate(); + return; + } + else { + this.spin(false, false); + } + this.field.fireEvent("spin", this); + this.field.fireEvent("spinup", this); }, - onHide: function(){ - Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments); - this.stopMonitoring(); - this.grid.getView().focusRow(this.rowIndex); + //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); }, - positionButtons: function(){ - if(this.btns){ - var h = this.el.dom.clientHeight; - var view = this.grid.getView(); - var scroll = view.scroller.dom.scrollLeft; - var width = view.mainBody.getWidth(); - var bw = this.btns.getWidth(); - this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2}); + //private + onSpinUpAlternate: function(){ + if (this.isSpinnable() == false) { + return; } + this.spin(false, true); + this.field.fireEvent("spin", this); + this.field.fireEvent("spinup", this); }, - // private - preEditValue : function(r, field){ - var value = r.data[field]; - return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value; + //private + onSpinDownAlternate: function(){ + if (this.isSpinnable() == false) { + return; + } + this.spin(true, true); + this.field.fireEvent("spin", this); + this.field.fireEvent("spindown", this); }, - // private - postEditValue : function(value, originalValue, r, field){ - return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value; + 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); }, - doFocus: function(pt){ - if(this.isVisible()){ - var index = 0; - if(pt){ - index = this.getTargetColumnIndex(pt); - } - var cm = this.grid.getColumnModel(); - for(var i = index||0, len = cm.getColumnCount(); i < len; i++){ - var c = cm.getColumnAt(i); - if(!c.hidden && c.getEditor()){ - c.getEditor().focus(); - break; - } - } + 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); }, - getTargetColumnIndex: function(pt){ - var grid = this.grid, v = grid.view; - var x = pt.left; - var cms = grid.colModel.config; - var i = 0, match = false; - for(var len = cms.length, c; c = cms[i]; i++){ - if(!c.hidden){ - if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){ - match = i; - break; - } - } + // private + fixPrecision: function(value){ + var nan = isNaN(value); + if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) { + return nan ? '' : value; } - return match; + return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision)); }, - startMonitoring : function(){ - if(!this.bound && this.monitorValid){ - this.bound = true; - Ext.TaskMgr.start({ - run : this.bindHandler, - interval : this.monitorPoll || 200, - scope: this - }); + doDestroy: function(){ + if (this.trigger) { + this.trigger.remove(); + } + if (this.wrap) { + this.wrap.remove(); + delete this.field.wrap; } - }, - stopMonitoring : function(){ - this.bound = false; - if(this.tooltip){ - this.tooltip.hide(); + if (this.splitter) { + this.splitter.remove(); } - }, - isValid: function(){ - var valid = true; - this.items.each(function(f){ - if(!f.isValid(true)){ - valid = false; - return false; - } - }); - return valid; - }, + if (this.dd) { + this.dd.unreg(); + this.dd = null; + } - // private - bindHandler : function(){ - if(!this.bound){ - return false; // stops binding + if (this.proxy) { + this.proxy.remove(); } - var valid = this.isValid(); - if(!valid && this.errorSummary){ - this.showTooltip(this.getErrorText().join('')); + + if (this.repeater) { + this.repeater.purgeListeners(); } - this.btns.saveBtn.setDisabled(!valid); - this.fireEvent('validation', this, valid); + } +}); + +//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]); }, - showTooltip: function(msg){ - var t = this.tooltip; - if(!t){ - t = this.tooltip = new Ext.ToolTip({ - maxWidth: 600, - cls: 'errorTip', - width: 300, - title: 'Errors', - autoHide: false, - anchor: 'left', - anchorToTarget: true, - mouseOffset: [40,0] - }); + show : function(el, callback, scope){ + if(this.animated){ + this.show.defer(50, this, [el, callback, scope]); + return; } - t.initTarget(this.items.last().getEl()); - if(!t.rendered){ - t.show(); - t.hide(); + 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 } - t.body.update(msg); - t.doAutoWidth(); - t.show(); }, - getErrorText: function(){ - var data = ['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']
+ })
});
-Ext.override(Ext.ToolTip, {
- doAutoWidth : function(){
- var bw = this.body.getTextWidth();
- if(this.title){
- bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
- }
- bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
- this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
+// 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
+});
- // IE7 repaint bug on initial show
- if(Ext.isIE7 && !this.repainted){
- this.el.repaint();
- this.repainted = true;
- }
- }
+// 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']
+ })
});
-Ext.ns('Ext.ux.grid');
-
-/**
- * @class Ext.ux.grid.RowExpander
- * @extends Ext.util.Observable
- * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
- * a second row body which expands/contracts. The expand/contract behavior is configurable to react
- * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
- *
- * @ptype rowexpander
- */
-Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {
- /**
- * @cfg {Boolean} expandOnEnter
- * true to toggle selected row(s) between expanded/collapsed when the enter
- * key is pressed (defaults to true).
- */
- expandOnEnter : true,
- /**
- * @cfg {Boolean} expandOnDblClick
- * true to toggle a row between expanded/collapsed when double clicked
- * (defaults to true).
- */
- expandOnDblClick : true,
-
- header : '',
- width : 20,
- sortable : false,
- fixed : true,
- menuDisabled : true,
- dataIndex : '',
- id : 'expander',
- lazyRender : true,
- enableCaching : true,
-
- constructor: function(config){
- Ext.apply(this, config);
-
- this.addEvents({
- /**
- * @event beforeexpand
- * Fires before the row expands. Have the listener return false to prevent the row from expanding.
- * @param {Object} this RowExpander object.
- * @param {Object} Ext.data.Record Record for the selected row.
- * @param {Object} body body element for the secondary row.
- * @param {Number} rowIndex The current row index.
- */
- beforeexpand: true,
- /**
- * @event expand
- * Fires after the row expands.
- * @param {Object} this RowExpander object.
- * @param {Object} Ext.data.Record Record for the selected row.
- * @param {Object} body body element for the secondary row.
- * @param {Number} rowIndex The current row index.
- */
- expand: true,
- /**
- * @event beforecollapse
- * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.
- * @param {Object} this RowExpander object.
- * @param {Object} Ext.data.Record Record for the selected row.
- * @param {Object} body body element for the secondary row.
- * @param {Number} rowIndex The current row index.
- */
- beforecollapse: true,
- /**
- * @event collapse
- * Fires after the row collapses.
- * @param {Object} this RowExpander object.
- * @param {Object} Ext.data.Record Record for the selected row.
- * @param {Object} body body element for the secondary row.
- * @param {Number} rowIndex The current row index.
- */
- collapse: true
- });
-
- Ext.ux.grid.RowExpander.superclass.constructor.call(this);
-
- if(this.tpl){
- if(typeof this.tpl == 'string'){
- this.tpl = new Ext.Template(this.tpl);
- }
- this.tpl.compile();
- }
-
- this.state = {};
- this.bodyContent = {};
- },
-
- getRowClass : function(record, rowIndex, p, ds){
- p.cols = p.cols-1;
- var content = this.bodyContent[record.id];
- if(!content && !this.lazyRender){
- content = this.getBodyContent(record, rowIndex);
- }
- if(content){
- p.body = content;
- }
- return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
- },
-
- init : function(grid){
- this.grid = grid;
-
- var view = grid.getView();
- view.getRowClass = this.getRowClass.createDelegate(this);
-
- view.enableRowBody = true;
-
-
- grid.on('render', this.onRender, this);
- grid.on('destroy', this.onDestroy, this);
- },
-
- // @private
- onRender: function() {
- var grid = this.grid;
- var mainBody = grid.getView().mainBody;
- mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});
- if (this.expandOnEnter) {
- this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {
- 'enter' : this.onEnter,
- scope: this
- });
- }
- if (this.expandOnDblClick) {
- grid.on('rowdblclick', this.onRowDblClick, this);
- }
- },
-
- // @private
- onDestroy: function() {
- this.keyNav.disable();
- delete this.keyNav;
- var mainBody = this.grid.getView().mainBody;
- mainBody.un('mousedown', this.onMouseDown, this);
- },
- // @private
- onRowDblClick: function(grid, rowIdx, e) {
- this.toggleRow(rowIdx);
- },
-
- onEnter: function(e) {
- var g = this.grid;
- var sm = g.getSelectionModel();
- var sels = sm.getSelections();
- for (var i = 0, len = sels.length; i < len; i++) {
- var rowIdx = g.getStore().indexOf(sels[i]);
- this.toggleRow(rowIdx);
- }
- },
-
- getBodyContent : function(record, index){
- if(!this.enableCaching){
- return this.tpl.apply(record.data);
- }
- var content = this.bodyContent[record.id];
- if(!content){
- content = this.tpl.apply(record.data);
- this.bodyContent[record.id] = content;
- }
- return content;
- },
-
- onMouseDown : function(e, t){
- e.stopEvent();
- var row = e.getTarget('.x-grid3-row');
- this.toggleRow(row);
- },
-
- renderer : function(v, p, record){
- p.cellAttr = 'rowspan="2"';
- return '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
- }]
+// 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;
+}
-// 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
- }]
+// 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'
});
- */
-Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ */
+
+ /**
+ * @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
- monitorResize:true,
+ activeThreadId : 0,
// private
- isValidParent : function(c, target){
- return c.getEl().dom.parentNode == this.innerCt.dom;
+ initComponent : function(){
+ if(this.statusAlign=='right'){
+ this.cls += ' x-status-right';
+ }
+ Ext.ux.StatusBar.superclass.initComponent.call(this);
},
// private
- onLayout : function(ct, target){
- var rs = ct.items.items, len = rs.length, r, i;
+ afterRender : function(){
+ Ext.ux.StatusBar.superclass.afterRender.call(this);
- if(!this.innerCt){
- target.addClass('ux-row-layout-ct');
- this.innerCt = target.createChild({cls:'x-row-inner'});
+ 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.renderAll(ct, this.innerCt);
+ this.doLayout();
+ },
- var size = target.getViewSize();
+ /**
+ * 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');
- if(size.width < 1 && size.height < 1){ // display none?
- return;
- }
+// Set the status and icon, auto-clearing with default options:
+statusBar.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom',
+ clear: true
+});
- var h = size.height - target.getPadding('tb'),
- ph = h;
+// 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 || {};
- this.innerCt.setSize({height:h});
+ if(typeof o == 'string'){
+ o = {text:o};
+ }
+ if(o.text !== undefined){
+ this.setText(o.text);
+ }
+ if(o.iconCls !== undefined){
+ this.setIcon(o.iconCls);
+ }
- // some rows can be percentages while others are fixed
- // so we need to make 2 passes
+ if(o.clear){
+ var c = o.clear,
+ wait = this.autoClear,
+ defaults = {useDefaults: true, anim: true};
- for(i = 0; i < len; i++){
- r = rs[i];
- if(!r.rowHeight){
- ph -= (r.getSize().height + r.getEl().getMargins('tb'));
+ 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;
+ },
- ph = ph < 0 ? 0 : ph;
+ /**
+ * 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 || {};
- for(i = 0; i < len; i++){
- r = rs[i];
- if(r.rowHeight){
- r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
- }
+ 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',
/**
- * @property activeItem
- * @hide
+ * @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.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
-Ext.ns('Ext.ux.form');
-
-Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {
- initComponent : function(){
- Ext.ux.form.SearchField.superclass.initComponent.call(this);
- this.on('specialkey', function(f, e){
- if(e.getKey() == e.ENTER){
- this.onTrigger2Click();
- }
- }, this);
- },
-
- validationEvent:false,
- validateOnBlur:false,
- trigger1Class:'x-form-clear-trigger',
- trigger2Class:'x-form-search-trigger',
- hideTrigger1:true,
- width:180,
- hasSearch : false,
- paramName : 'query',
-
- onTrigger1Click : function(){
- if(this.hasSearch){
- this.el.dom.value = '';
- var o = {start: 0};
- this.store.baseParams = this.store.baseParams || {};
- this.store.baseParams[this.paramName] = '';
- this.store.reload({params:o});
- this.triggers[0].hide();
- this.hasSearch = false;
- }
- },
-
- onTrigger2Click : function(){
- var v = this.getRawValue();
- if(v.length < 1){
- this.onTrigger1Click();
- return;
- }
- var o = {start: 0};
- this.store.baseParams = this.store.baseParams || {};
- this.store.baseParams[this.paramName] = v;
- this.store.reload({params:o});
- this.hasSearch = true;
- this.triggers[0].show();
- }
-});Ext.ns('Ext.ux.form');
-
-/**
- * @class Ext.ux.form.SelectBox
- * @extends Ext.form.ComboBox
- *
Makes a ComboBox more closely mimic an HTML SELECT. Supports clicking and dragging
- * through the list, with item selection occurring when the mouse button is released.
- * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}
- * on inner elements. Re-enabling editable after calling this will NOT work.
- * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392
- * @history 2007-07-08 jvs
- * Slight mods for Ext 2.0
- * @xtype selectbox
- */
-Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {
- constructor: function(config){
- this.searchResetDelay = 1000;
- config = config || {};
- config = Ext.apply(config || {}, {
- editable: false,
- forceSelection: true,
- rowHeight: false,
- lastSearchTerm: false,
- triggerAction: 'all',
- mode: 'local'
- });
-
- Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);
-
- this.lastSelectedIndex = this.selectedIndex || 0;
- },
-
- initEvents : function(){
- Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);
- // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
- this.el.on('keydown', this.keySearch, this, true);
- this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);
- },
-
- keySearch : function(e, target, options) {
- var raw = e.getKey();
- var key = String.fromCharCode(raw);
- var startIndex = 0;
-
- if( !this.store.getCount() ) {
- return;
- }
-
- switch(raw) {
- case Ext.EventObject.HOME:
- e.stopEvent();
- this.selectFirst();
- return;
-
- case Ext.EventObject.END:
- e.stopEvent();
- this.selectLast();
- return;
-
- case Ext.EventObject.PAGEDOWN:
- this.selectNextPage();
- e.stopEvent();
- return;
-
- case Ext.EventObject.PAGEUP:
- this.selectPrevPage();
- e.stopEvent();
- return;
- }
-
- // skip special keys other than the shift key
- if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
- return;
- }
- if( this.lastSearchTerm == key ) {
- startIndex = this.lastSelectedIndex;
- }
- this.search(this.displayField, key, startIndex);
- this.cshTask.delay(this.searchResetDelay);
- },
-
- onRender : function(ct, position) {
- this.store.on('load', this.calcRowsPerPage, this);
- Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);
- if( this.mode == 'local' ) {
- this.calcRowsPerPage();
- }
- },
-
- onSelect : function(record, index, skipCollapse){
- if(this.fireEvent('beforeselect', this, record, index) !== false){
- this.setValue(record.data[this.valueField || this.displayField]);
- if( !skipCollapse ) {
- this.collapse();
- }
- this.lastSelectedIndex = index + 1;
- this.fireEvent('select', this, record, index);
- }
- },
-
- render : function(ct) {
- Ext.ux.form.SelectBox.superclass.render.apply(this, arguments);
- if( Ext.isSafari ) {
- this.el.swallowEvent('mousedown', true);
- }
- this.el.unselectable();
- this.innerList.unselectable();
- this.trigger.unselectable();
- this.innerList.on('mouseup', function(e, target, options) {
- if( target.id && target.id == this.innerList.id ) {
- return;
- }
- this.onViewClick();
- }, this);
-
- this.innerList.on('mouseover', function(e, target, options) {
- if( target.id && target.id == this.innerList.id ) {
- return;
- }
- this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
- this.cshTask.delay(this.searchResetDelay);
- }, this);
-
- this.trigger.un('click', this.onTriggerClick, this);
- this.trigger.on('mousedown', function(e, target, options) {
- e.preventDefault();
- this.onTriggerClick();
- }, this);
-
- this.on('collapse', function(e, target, options) {
- Ext.getDoc().un('mouseup', this.collapseIf, this);
- }, this, true);
-
- this.on('expand', function(e, target, options) {
- Ext.getDoc().on('mouseup', this.collapseIf, this);
- }, this, true);
- },
-
- clearSearchHistory : function() {
- this.lastSelectedIndex = 0;
- this.lastSearchTerm = false;
- },
-
- selectFirst : function() {
- this.focusAndSelect(this.store.data.first());
- },
-
- selectLast : function() {
- this.focusAndSelect(this.store.data.last());
- },
-
- selectPrevPage : function() {
- if( !this.rowHeight ) {
- return;
- }
- var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);
- this.focusAndSelect(this.store.getAt(index));
- },
-
- selectNextPage : function() {
- if( !this.rowHeight ) {
- return;
- }
- var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);
- this.focusAndSelect(this.store.getAt(index));
- },
-
- search : function(field, value, startIndex) {
- field = field || this.displayField;
- this.lastSearchTerm = value;
- var index = this.store.find.apply(this.store, arguments);
- if( index !== -1 ) {
- this.focusAndSelect(index);
- }
- },
-
- focusAndSelect : function(record) {
- var index = typeof record === 'number' ? record : this.store.indexOf(record);
- this.select(index, this.isExpanded());
- this.onSelect(this.store.getAt(record), index, this.isExpanded());
- },
-
- calcRowsPerPage : function() {
- if( this.store.getCount() ) {
- this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
- this.rowsPerPage = this.maxHeight / this.rowHeight;
- } else {
- this.rowHeight = false;
- }
- }
-
-});
-
-Ext.reg('selectbox', Ext.ux.form.SelectBox);
-
-//backwards compat
-Ext.ux.SelectBox = Ext.ux.form.SelectBox;
-/**
- * @class Ext.ux.SliderTip
- * @extends Ext.Tip
- * Simple plugin for using an Ext.Tip with a slider to show the slider value
- */
-Ext.ux.SliderTip = Ext.extend(Ext.Tip, {
- minWidth: 10,
- offsets : [0, -10],
- init : function(slider){
- slider.on('dragstart', this.onSlide, this);
- slider.on('drag', this.onSlide, this);
- slider.on('dragend', this.hide, this);
- slider.on('destroy', this.destroy, this);
- },
-
- onSlide : function(slider){
- this.show();
- this.body.update(this.getText(slider));
- this.doAutoWidth();
- this.el.alignTo(slider.thumb, 'b-t?', this.offsets);
- },
-
- getText : function(slider){
- return String(slider.getValue());
- }
-});
-Ext.ux.SlidingPager = Ext.extend(Object, {
- init : function(pbar){
- Ext.each(pbar.items.getRange(2,6), function(c){
- c.hide();
- });
- var slider = new Ext.Slider({
- width: 114,
- minValue: 1,
- maxValue: 1,
- plugins: new Ext.ux.SliderTip({
- getText : function(s){
- return String.format('Page {0} of {1}', s.value, s.maxValue);
- }
- }),
- listeners: {
- changecomplete: function(s, v){
- pbar.changePage(v);
- }
- }
- });
- pbar.insert(5, slider);
- pbar.on({
- change: function(pb, data){
- slider.maxValue = data.pages;
- slider.setValue(data.activePage);
- },
- beforedestroy: function(){
- slider.destroy();
- }
- });
- }
-});Ext.ns('Ext.ux.form');
-
-/**
- * @class Ext.ux.form.SpinnerField
- * @extends Ext.form.NumberField
- * Creates a field utilizing Ext.ux.Spinner
- * @xtype spinnerfield
- */
-Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
- deferHeight: true,
- autoSize: Ext.emptyFn,
- onBlur: Ext.emptyFn,
- adjustSize: Ext.BoxComponent.prototype.adjustSize,
-
- constructor: function(config) {
- var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');
-
- var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);
-
- var plugins = config.plugins
- ? (Ext.isArray(config.plugins)
- ? config.plugins.push(spl)
- : [config.plugins, spl])
- : spl;
-
- Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));
- },
-
- onShow: function(){
- if (this.wrap) {
- this.wrap.dom.style.display = '';
- this.wrap.dom.style.visibility = 'visible';
- }
- },
-
- onHide: function(){
- this.wrap.dom.style.display = 'none';
- },
-
- // private
- getResizeEl: function(){
- return this.wrap;
- },
-
- // private
- getPositionEl: function(){
- return this.wrap;
- },
-
- // private
- alignErrorIcon: function(){
- if (this.wrap) {
- this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
- }
- },
-
- validateBlur: function(){
- return true;
- }
-});
-
-Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);
-
-//backwards compat
-Ext.form.SpinnerField = Ext.ux.form.SpinnerField;
-/**
- * @class Ext.ux.Spinner
- * @extends Ext.util.Observable
- * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
- */
-Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
- incrementValue: 1,
- alternateIncrementValue: 5,
- triggerClass: 'x-form-spinner-trigger',
- splitterClass: 'x-form-spinner-splitter',
- alternateKey: Ext.EventObject.shiftKey,
- defaultValue: 0,
- accelerate: false,
-
- constructor: function(config){
- Ext.ux.Spinner.superclass.constructor.call(this, config);
- Ext.apply(this, config);
- this.mimicing = false;
- },
-
- init: function(field){
- this.field = field;
-
- field.afterMethod('onRender', this.doRender, this);
- field.afterMethod('onEnable', this.doEnable, this);
- field.afterMethod('onDisable', this.doDisable, this);
- field.afterMethod('afterRender', this.doAfterRender, this);
- field.afterMethod('onResize', this.doResize, this);
- field.afterMethod('onFocus', this.doFocus, this);
- field.beforeMethod('onDestroy', this.doDestroy, this);
- },
-
- doRender: function(ct, position){
- var el = this.el = this.field.getEl();
- var f = this.field;
-
- if (!f.wrap) {
- f.wrap = this.wrap = el.wrap({
- cls: "x-form-field-wrap"
- });
- }
- else {
- this.wrap = f.wrap.addClass('x-form-field-wrap');
- }
-
- this.trigger = this.wrap.createChild({
- tag: "img",
- src: Ext.BLANK_IMAGE_URL,
- cls: "x-form-trigger " + this.triggerClass
- });
-
- if (!f.width) {
- this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
- }
-
- this.splitter = this.wrap.createChild({
- tag: 'div',
- cls: this.splitterClass,
- style: 'width:13px; height:2px;'
- });
- this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();
-
- this.proxy = this.trigger.createProxy('', this.splitter, true);
- this.proxy.addClass("x-form-spinner-proxy");
- this.proxy.setStyle('left', '0px');
- this.proxy.setSize(14, 1);
- this.proxy.hide();
- this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {
- dragElId: this.proxy.id
- });
-
- this.initTrigger();
- this.initSpinner();
- },
-
- doAfterRender: function(){
- var y;
- if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
- this.el.position();
- this.el.setY(y);
- }
- },
-
- doEnable: function(){
- if (this.wrap) {
- this.wrap.removeClass(this.field.disabledClass);
- }
- },
-
- doDisable: function(){
- if (this.wrap) {
- this.wrap.addClass(this.field.disabledClass);
- this.el.removeClass(this.field.disabledClass);
- }
- },
-
- doResize: function(w, h){
- if (typeof w == 'number') {
- this.el.setWidth(this.field.adjustWidth('input', w - this.trigger.getWidth()));
- }
- this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
- },
-
- doFocus: function(){
- if (!this.mimicing) {
- this.wrap.addClass('x-trigger-wrap-focus');
- this.mimicing = true;
- Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {
- delay: 10
- });
- this.el.on('keydown', this.checkTab, this);
- }
- },
-
- // private
- checkTab: function(e){
- if (e.getKey() == e.TAB) {
- this.triggerBlur();
- }
- },
-
- // private
- mimicBlur: function(e){
- if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
- this.triggerBlur();
- }
- },
-
- // private
- triggerBlur: function(){
- this.mimicing = false;
- Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
- this.el.un("keydown", this.checkTab, this);
- this.field.beforeBlur();
- this.wrap.removeClass('x-trigger-wrap-focus');
- this.field.onBlur.call(this.field);
- },
-
- initTrigger: function(){
- this.trigger.addClassOnOver('x-form-trigger-over');
- this.trigger.addClassOnClick('x-form-trigger-click');
- },
-
- initSpinner: function(){
- this.field.addEvents({
- 'spin': true,
- 'spinup': true,
- 'spindown': true
- });
-
- this.keyNav = new Ext.KeyNav(this.el, {
- "up": function(e){
- e.preventDefault();
- this.onSpinUp();
- },
-
- "down": function(e){
- e.preventDefault();
- this.onSpinDown();
- },
-
- "pageUp": function(e){
- e.preventDefault();
- this.onSpinUpAlternate();
- },
-
- "pageDown": function(e){
- e.preventDefault();
- this.onSpinDownAlternate();
- },
-
- scope: this
- });
-
- this.repeater = new Ext.util.ClickRepeater(this.trigger, {
- accelerate: this.accelerate
- });
- this.field.mon(this.repeater, "click", this.onTriggerClick, this, {
- preventDefault: true
- });
-
- this.field.mon(this.trigger, {
- mouseover: this.onMouseOver,
- mouseout: this.onMouseOut,
- mousemove: this.onMouseMove,
- mousedown: this.onMouseDown,
- mouseup: this.onMouseUp,
- scope: this,
- preventDefault: true
- });
-
- this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);
-
- this.dd.setXConstraint(0, 0, 10)
- this.dd.setYConstraint(1500, 1500, 10);
- this.dd.endDrag = this.endDrag.createDelegate(this);
- this.dd.startDrag = this.startDrag.createDelegate(this);
- this.dd.onDrag = this.onDrag.createDelegate(this);
- },
-
- onMouseOver: function(){
- if (this.disabled) {
- return;
- }
- var middle = this.getMiddle();
- this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
- this.trigger.addClass(this.tmpHoverClass);
- },
-
- //private
- onMouseOut: function(){
- this.trigger.removeClass(this.tmpHoverClass);
- },
-
- //private
- onMouseMove: function(){
- if (this.disabled) {
- return;
- }
- var middle = this.getMiddle();
- if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||
- ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {
- }
- },
-
- //private
- onMouseDown: function(){
- if (this.disabled) {
- return;
- }
- var middle = this.getMiddle();
- this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
- this.trigger.addClass(this.tmpClickClass);
- },
-
- //private
- onMouseUp: function(){
- this.trigger.removeClass(this.tmpClickClass);
- },
-
- //private
- onTriggerClick: function(){
- if (this.disabled || this.el.dom.readOnly) {
- return;
- }
- var middle = this.getMiddle();
- var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
- this['onSpin' + ud]();
- },
-
- //private
- getMiddle: function(){
- var t = this.trigger.getTop();
- var h = this.trigger.getHeight();
- var middle = t + (h / 2);
- return middle;
- },
-
- //private
- //checks if control is allowed to spin
- isSpinnable: function(){
- if (this.disabled || this.el.dom.readOnly) {
- Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
- return false;
- }
- return true;
- },
-
- handleMouseWheel: function(e){
- //disable scrolling when not focused
- if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
- return;
- }
-
- var delta = e.getWheelDelta();
- if (delta > 0) {
- this.onSpinUp();
- e.stopEvent();
- }
- else
- if (delta < 0) {
- this.onSpinDown();
- e.stopEvent();
- }
- },
-
- //private
- startDrag: function(){
- this.proxy.show();
- this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
- },
-
- //private
- endDrag: function(){
- this.proxy.hide();
- },
-
- //private
- onDrag: function(){
- if (this.disabled) {
- return;
- }
- var y = Ext.fly(this.dd.getDragEl()).getTop();
- var ud = '';
-
- if (this._previousY > y) {
- ud = 'Up';
- } //up
- if (this._previousY < y) {
- ud = 'Down';
- } //down
- if (ud != '') {
- this['onSpin' + ud]();
- }
-
- this._previousY = y;
- },
-
- //private
- onSpinUp: function(){
- if (this.isSpinnable() == false) {
- return;
- }
- if (Ext.EventObject.shiftKey == true) {
- this.onSpinUpAlternate();
- return;
- }
- else {
- this.spin(false, false);
- }
- this.field.fireEvent("spin", this);
- this.field.fireEvent("spinup", this);
- },
-
- //private
- onSpinDown: function(){
- if (this.isSpinnable() == false) {
- return;
- }
- if (Ext.EventObject.shiftKey == true) {
- this.onSpinDownAlternate();
- return;
- }
- else {
- this.spin(true, false);
- }
- this.field.fireEvent("spin", this);
- this.field.fireEvent("spindown", this);
- },
-
- //private
- onSpinUpAlternate: function(){
- if (this.isSpinnable() == false) {
- return;
- }
- this.spin(false, true);
- this.field.fireEvent("spin", this);
- this.field.fireEvent("spinup", this);
- },
-
- //private
- onSpinDownAlternate: function(){
- if (this.isSpinnable() == false) {
- return;
- }
- this.spin(true, true);
- this.field.fireEvent("spin", this);
- this.field.fireEvent("spindown", this);
- },
-
- spin: function(down, alternate){
- var v = parseFloat(this.field.getValue());
- var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
- (down == true) ? v -= incr : v += incr;
-
- v = (isNaN(v)) ? this.defaultValue : v;
- v = this.fixBoundries(v);
- this.field.setRawValue(v);
- },
-
- fixBoundries: function(value){
- var v = value;
-
- if (this.field.minValue != undefined && v < this.field.minValue) {
- v = this.field.minValue;
- }
- if (this.field.maxValue != undefined && v > this.field.maxValue) {
- v = this.field.maxValue;
- }
-
- return this.fixPrecision(v);
- },
-
- // private
- fixPrecision: function(value){
- var nan = isNaN(value);
- if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {
- return nan ? '' : value;
- }
- return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));
- },
-
- doDestroy: function(){
- if (this.trigger) {
- this.trigger.remove();
- }
- if (this.wrap) {
- this.wrap.remove();
- delete this.field.wrap;
- }
-
- if (this.splitter) {
- this.splitter.remove();
- }
-
- if (this.dd) {
- this.dd.unreg();
- this.dd = null;
- }
-
- if (this.proxy) {
- this.proxy.remove();
- }
-
- if (this.repeater) {
- this.repeater.purgeListeners();
- }
- }
-});
-
-//backwards compat
-Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){
- Ext.apply(this, config);
-}
-Ext.ux.Spotlight.prototype = {
- active : false,
- animate : true,
- duration: .25,
- easing:'easeNone',
-
- // private
- animated : false,
-
- createElements : function(){
- var bd = Ext.getBody();
-
- this.right = bd.createChild({cls:'x-spotlight'});
- this.left = bd.createChild({cls:'x-spotlight'});
- this.top = bd.createChild({cls:'x-spotlight'});
- this.bottom = bd.createChild({cls:'x-spotlight'});
-
- this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);
- },
-
- show : function(el, callback, scope){
- if(this.animated){
- this.show.defer(50, this, [el, callback, scope]);
- return;
- }
- this.el = Ext.get(el);
- if(!this.right){
- this.createElements();
- }
- if(!this.active){
- this.all.setDisplayed('');
- this.applyBounds(true, false);
- this.active = true;
- Ext.EventManager.onWindowResize(this.syncSize, this);
- this.applyBounds(false, this.animate, false, callback, scope);
- }else{
- this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous
- }
- },
-
- hide : function(callback, scope){
- if(this.animated){
- this.hide.defer(50, this, [callback, scope]);
- return;
- }
- Ext.EventManager.removeResizeListener(this.syncSize, this);
- this.applyBounds(true, this.animate, true, callback, scope);
- },
-
- doHide : function(){
- this.active = false;
- this.all.setDisplayed(false);
- },
-
- syncSize : function(){
- this.applyBounds(false, false);
- },
-
- applyBounds : function(basePts, anim, doHide, callback, scope){
-
- var rg = this.el.getRegion();
-
- var dw = Ext.lib.Dom.getViewWidth(true);
- var dh = Ext.lib.Dom.getViewHeight(true);
-
- var c = 0, cb = false;
- if(anim){
- cb = {
- callback: function(){
- c++;
- if(c == 4){
- this.animated = false;
- if(doHide){
- this.doHide();
- }
- Ext.callback(callback, scope, [this]);
- }
- },
- scope: this,
- duration: this.duration,
- easing: this.easing
- };
- this.animated = true;
- }
-
- this.right.setBounds(
- rg.right,
- basePts ? dh : rg.top,
- dw - rg.right,
- basePts ? 0 : (dh - rg.top),
- cb);
-
- this.left.setBounds(
- 0,
- 0,
- rg.left,
- basePts ? 0 : rg.bottom,
- cb);
-
- this.top.setBounds(
- basePts ? dw : rg.left,
- 0,
- basePts ? 0 : dw - rg.left,
- rg.top,
- cb);
-
- this.bottom.setBounds(
- 0,
- rg.bottom,
- basePts ? 0 : rg.right,
- dh - rg.bottom,
- cb);
-
- if(!anim){
- if(doHide){
- this.doHide();
- }
- if(callback){
- Ext.callback(callback, scope, [this]);
- }
- }
- },
-
- destroy : function(){
- this.doHide();
- Ext.destroy(
- this.right,
- this.left,
- this.top,
- this.bottom);
- delete this.el;
- delete this.all;
- }
-};
-
-//backwards compat
-Ext.Spotlight = Ext.ux.Spotlight;/**
- * @class Ext.ux.TabCloseMenu
- * @extends Object
- * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.
- *
- * @ptype tabclosemenu
- */
-Ext.ux.TabCloseMenu = function(){
- var tabs, menu, ctxItem;
- this.init = function(tp){
- tabs = tp;
- tabs.on('contextmenu', onContextMenu);
- };
-
- function onContextMenu(ts, item, e){
- if(!menu){ // create context menu on first right click
- menu = new Ext.menu.Menu({
- items: [{
- id: tabs.id + '-close',
- text: 'Close Tab',
- handler : function(){
- tabs.remove(ctxItem);
- }
- },{
- id: tabs.id + '-close-others',
- text: 'Close Other Tabs',
- handler : function(){
- tabs.items.each(function(item){
- if(item.closable && item != ctxItem){
- tabs.remove(item);
- }
- });
- }
- }]});
- }
- ctxItem = item;
- var items = menu.items;
- items.get(tabs.id + '-close').setDisabled(!item.closable);
- var disableOthers = true;
- tabs.items.each(function(){
- if(this != item && this.closable){
- disableOthers = false;
- return false;
- }
- });
- items.get(tabs.id + '-close-others').setDisabled(disableOthers);
- e.stopEvent();
- menu.showAt(e.getPoint());
- }
-};
-
-Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);
-Ext.ns('Ext.ux.grid');
+Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);Ext.ns('Ext.ux.grid');
/**
* @class Ext.ux.grid.TableGrid
@@ -5968,18 +9202,35 @@ 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.tabPanelMethods);
+ Ext.apply(tabPanel, this.parentOverrides);
tabPanel.tabScrollerMenu = this;
var thisRef = this;
@@ -6027,45 +9278,66 @@ Ext.ux.TabScrollerMenu = Ext.extend(Object, {
});
},
- // public
+ /**
+ * Returns an the current page size (this.pageSize);
+ * @return {Number} this.pageSize The current page size.
+ */
getPageSize : function() {
return this.pageSize;
},
- // public
- setPageSize : function(pageSize) {
+ /**
+ * Sets the number of menu items per submenu "page size".
+ * @param {Number} pageSize The page size
+ */
+ setPageSize : function(pageSize) {
this.pageSize = pageSize;
},
- // public
- getMaxText : function() {
+ /**
+ * Returns the current maxText length;
+ * @return {Number} this.maxText The current max text length.
+ */
+ getMaxText : function() {
return this.maxText;
},
- // public
- setMaxText : function(t) {
+ /**
+ * 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.
- tabPanelMethods : {
+ parentOverrides : {
// all execute within the scope of the tab panel
// private
showTabsMenu : function(e) {
- if (! this.tabsMenu) {
- this.tabsMenu = new Ext.menu.Menu();
- this.on('beforedestroy', this.tabsMenu.destroy, this.tabsMenu);
+ if (this.tabsMenu) {
+ this.tabsMenu.destroy();
+ this.un('destroy', this.tabsMenu.destroy, this.tabsMenu);
+ this.tabsMenu = null;
}
-
- this.tabsMenu.removeAll();
-
- this.generateTabMenuItems();
-
- var target = Ext.get(e.getTarget());
+ 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;
@@ -6109,143 +9381,1122 @@ Ext.ux.TabScrollerMenu = Ext.extend(Object, {
menuItems.push(this.autoGenMenuItem(item));
}
-
this.tabsMenu.add({
text : this.tabScrollerMenu.menuPrefixText + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
menu : menuItems
});
-
- }
- }
- else {
- this.items.each(function(item) {
- if (item.id != curActive.id && ! item.hidden) {
- menuItems.push(this.autoGenMenuItem(item));
- }
- }, this);
- }
- },
- // private
- autoGenMenuItem : function(item) {
- var maxText = this.tabScrollerMenu.getMaxText();
- var text = Ext.util.Format.ellipsis(item.title, maxText);
-
- return {
- text : text,
- handler : this.showTabFromMenu,
- scope : this,
- disabled : item.disabled,
- tabToShow : item,
- iconCls : item.iconCls
- }
-
- },
- // private
- showTabFromMenu : function(menuItem) {
- this.setActiveTab(menuItem.tabToShow);
- }
- }
-});
-Ext.ns('Ext.ux.tree');
+ }
+ }
+ 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(
+ ' ',
+ ' ');
+
+ 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.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;
+
+ this.proxy.remove();
+ delete this.dragHd;
+
+ tree.columns[this.hdIndex].width = nw;
+ tree.updateColumnWidths();
+
+ setTimeout(function(){
+ tree.headersDisabled = false;
+ }, 100);
+ }
+});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.
+ * @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
- * Creates a new XmlTreeloader.
- * @param {Object} config A config object containing config properties.
+ * @param {TreeGrid} tree
+ * @param {Object} config
*/
-Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
+Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
/**
- * @property XML_NODE_ELEMENT
- * XML element node (value 1, read-only)
- * @type Number
+ * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to ['sort-asc', 'sort-desc'])
*/
- XML_NODE_ELEMENT : 1,
+ sortClasses : ['sort-asc', 'sort-desc'],
/**
- * @property XML_NODE_TEXT
- * XML text node (value 3, read-only)
- * @type Number
+ * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to 'Sort Ascending')
*/
- XML_NODE_TEXT : 3,
+ sortAscText : 'Sort Ascending',
+ /**
+ * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to 'Sort Descending')
+ */
+ sortDescText : 'Sort Descending',
- // private override
- processResponse : function(response, node, callback){
- var xmlData = response.responseXML;
- var root = xmlData.documentElement || xmlData;
+ constructor : function(tree, config) {
+ if(!Ext.isObject(config)) {
+ config = {
+ property: tree.columns[0].dataIndex || 'text',
+ folderSort: true
+ }
+ }
- try{
- node.beginUpdate();
- node.appendChild(this.parseXml(root));
- node.endUpdate();
+ Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments);
+
+ this.tree = tree;
+ tree.on('headerclick', this.onHeaderClick, this);
+ tree.ddAppendOnly = true;
+
+ me = this;
+ this.defaultSortFn = function(n1, n2){
- if(typeof callback == "function"){
- callback(this, node);
+ var dsc = me.dir && me.dir.toLowerCase() == 'desc';
+ var p = me.property || 'text';
+ var sortType = me.sortType;
+ var fs = me.folderSort;
+ var cs = me.caseSensitive === true;
+ var leafAttr = me.leafAttr || 'leaf';
+
+ if(fs){
+ if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
+ return 1;
+ }
+ if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
+ return -1;
+ }
}
- }catch(e){
- this.handleFailure(response);
+ var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
+ var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
+ if(v1 < v2){
+ return dsc ? +1 : -1;
+ }else if(v1 > v2){
+ return dsc ? -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
- 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);
- }
+ updateSortIcon : function(col, dir){
+ var sc = this.sortClasses;
+ var 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);
+ }
+ else if(l) {
+ l.createNode = function(attr) {
+ if (!attr.uiProvider) {
+ attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
}
- nodes.push(treeNode);
+ return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
}
- else if(n.nodeType == this.XML_NODE_TEXT){
- var text = n.nodeValue.trim();
- if(text.length > 0){
- return nodes = text;
- }
+ }
+ 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(
+ '',
+ '',
+ '',
+ '',
+ '
',
+ ''
+ );
+ }
+
+ 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);
}
- }, this);
+ c.init(this);
+ columns.push(c);
+
+ if(this.enableSort !== false && c.sortable !== false) {
+ c.sortable = true;
+ this.enableSort = true;
+ }
+ }
- return nodes;
+ this.columns = columns;
},
- // private override
- createNode : function(node){
- var attr = {
- tagName: node.tagName
- };
+ onRender : function(){
+ Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
- Ext.each(node.attributes, function(a){
- attr[a.nodeName] = a.nodeValue;
+ 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.header.dom.style.display = '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);
+ }
+ },
- this.processAttributes(attr);
+ 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);
- return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
+ 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;
- /*
- * 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).
+ 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
*/
- processAttributes: Ext.emptyFn
+ 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);
+ }
+ }
});
-//backwards compat
-Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;
+Ext.reg('treegrid', Ext.ux.tree.TreeGrid);
\ No newline at end of file
';
- },
-
- 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
- *