X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/2e847cf21b8ab9d15fa167b315ca5b2fa92638fc..ddde20c4d4ac6a8d53de079761155de813845b3c:/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 5849630f..d2878892 100644 --- a/examples/ux/ux-all-debug.js +++ b/examples/ux/ux-all-debug.js @@ -1,6 +1,6 @@ /*! - * Ext JS Library 3.1.1 - * Copyright(c) 2006-2010 Ext JS, LLC + * Ext JS Library 3.2.0 + * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ @@ -273,811 +273,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(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('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('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;
- },
-
- /**
- * 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;
- }
- } 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.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 json = this.grid.getStore().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){
- 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 || + onMapReady : function(){ + this.addMarkers(this.markers); + this.addMapControls(); + this.addOptions(); + }, + onResize : function(w, h){ + + if (typeof this.getMap() == 'object') { + this.gmap.checkResize(); + } + + Ext.ux.GMapPanel.superclass.onResize.call(this, w, h); + + }, + setSize : function(width, height, animate){ + + if (typeof this.getMap() == 'object') { + this.gmap.checkResize(); + } + + Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate); + + }, + getMap : function(){ + + return this.gmap; + + }, + getCenter : function(){ + + return this.getMap().getCenter(); + + }, + getCenterLatLng : function(){ + + var ll = this.getCenter(); + return {lat: ll.lat(), lng: ll.lng()}; + + }, + addMarkers : function(markers) { + + if (Ext.isArray(markers)){ + for (var i = 0; i < markers.length; i++) { + var mkr_point = new GLatLng(markers[i].lat,markers[i].lng); + this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners); + } + } + + }, + addMarker : function(point, marker, clear, center, listeners){ + + Ext.applyIf(marker,G_DEFAULT_ICON); + + if (clear === true){ + this.getMap().clearOverlays(); + } + if (center === true) { + this.getMap().setCenter(point, this.zoomLevel); + } + + var mark = new GMarker(point,marker); + if (typeof listeners === 'object'){ + for (evt in listeners) { + GEvent.bind(mark, evt, this, listeners[evt]); + } + } + this.getMap().addOverlay(mark); + + }, + addMapControls : function(){ + + if (this.gmapType === 'map') { + if (Ext.isArray(this.mapControls)) { + for(i=0;iGridFilter 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;
+ },
+
+ /**
+ * 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;
+ }
+ } 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 { @@ -6199,2249 +6286,2239 @@ Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, { 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; + + // 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 = function(portal, cfg){ + this.portal = portal; + Ext.dd.ScrollManager.register(portal.body); + Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg); + portal.body.ddScrollConfig = this.ddScrollConfig; +}; + +Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, { + ddScrollConfig : { + vthresh: 50, + hthresh: -1, + animate: true, + increment: 200 + }, + + createEvent : function(dd, e, data, col, c, pos){ + return { + portal: this.portal, + panel: data.panel, + columnIndex: col, + column: c, + position: pos, + data: data, + source: dd, + rawEvent: e, + status: this.dropAllowed + }; + }, + + notifyOver : function(dd, e, data){ + var xy = e.getXY(), portal = this.portal, px = dd.proxy; + + // case column widths + if(!this.grid){ + this.grid = this.getGrid(); + } + + // handle case scroll where scrollbars appear during drag + var cw = portal.body.dom.clientWidth; + if(!this.lastCW){ + this.lastCW = cw; + }else if(this.lastCW != cw){ + this.lastCW = cw; + portal.doLayout(); + this.grid = this.getGrid(); + } + + // determine column + var col = 0, xs = this.grid.columnX, cmatch = false; + for(var len = xs.length; col < len; col++){ + if(xy[0] < (xs[col].x + xs[col].w)){ + cmatch = true; + break; + } + } + // no match, fix last index + if(!cmatch){ + col--; + } + + // find insert position + var p, match = false, pos = 0, + c = portal.items.itemAt(col), + items = c.items.items, overSelf = false; + + for(var len = items.length; pos < len; pos++){ + p = items[pos]; + var h = p.el.getHeight(); + if(h === 0){ + overSelf = true; + } + else if((p.el.getY()+(h/2)) > xy[1]){ + match = true; + break; + } + } + + pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0); + var overEvent = this.createEvent(dd, e, data, col, c, pos); + + if(portal.fireEvent('validatedrop', overEvent) !== false && + portal.fireEvent('beforedragover', overEvent) !== false){ + + // make sure proxy width is fluid + px.getProxy().setWidth('auto'); + + if(p){ + px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null); + }else{ + px.moveProxy(c.el.dom, null); + } + + this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false}; + this.scrollPos = portal.body.getScroll(); + + portal.fireEvent('dragover', overEvent); + + return overEvent.status; + }else{ + return overEvent.status; + } + + }, + + notifyOut : function(){ + delete this.grid; + }, + + notifyDrop : function(dd, e, data){ + delete this.grid; + if(!this.lastPos){ + return; + } + var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p; + + var dropEvent = this.createEvent(dd, e, data, col, c, + pos !== false ? pos : c.items.getCount()); + + if(this.portal.fireEvent('validatedrop', dropEvent) !== false && + this.portal.fireEvent('beforedrop', dropEvent) !== false){ + + dd.proxy.getProxy().remove(); + dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom); + + if(pos !== false){ + if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){ + pos++; + } + c.insert(pos, dd.panel); + }else{ + c.add(dd.panel); + } + + c.doLayout(); + + this.portal.fireEvent('drop', dropEvent); + + // scroll position is lost on drop, fix it + var st = this.scrollPos.top; + if(st){ + var d = this.portal.body.dom; + setTimeout(function(){ + d.scrollTop = st; + }, 10); + } + + } + delete this.lastPos; + }, + + // internal cache of body and column coords + getGrid : function(){ + var box = this.portal.bwrap.getBox(); + box.columnX = []; + this.portal.items.each(function(c){ + box.columnX.push({x: c.el.getX(), w: c.el.getWidth()}); + }); + return box; + }, + + // unregister the dropzone from ScrollManager + unreg: function() { + //Ext.dd.ScrollManager.unregister(this.portal.body); + Ext.ux.Portal.DropZone.superclass.unreg.call(this); + } +}); +Ext.ux.PortalColumn = Ext.extend(Ext.Container, { + layout : 'anchor', + //autoEl : 'div',//already defined by Ext.Component + defaultType : 'portlet', + cls : 'x-portal-column' +}); + +Ext.reg('portalcolumn', Ext.ux.PortalColumn); +Ext.ux.Portlet = Ext.extend(Ext.Panel, { + anchor : '100%', + frame : true, + collapsible : true, + draggable : true, + cls : 'x-portlet' +}); + +Ext.reg('portlet', Ext.ux.Portlet); +/** +* @class Ext.ux.ProgressBarPager +* @extends Object +* Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text +* +* @ptype progressbarpager +* @constructor +* Create a new ItemSelector +* @param {Object} config Configuration options +* @xtype itemselector +*/ +Ext.ux.ProgressBarPager = Ext.extend(Object, { + /** + * @cfg {Integer} progBarWidth + *The default progress bar width. Default is 225.
+ */ + progBarWidth : 225, + /** + * @cfg {String} defaultText + *The text to display while the store is loading. Default is 'Loading...'
+ */ + defaultText : 'Loading...', + /** + * @cfg {Object} defaultAnimCfg + *A {@link Ext.Fx Ext.Fx} configuration object. Default is { duration : 1, easing : 'bounceOut' }.
+ */ + defaultAnimCfg : { + duration : 1, + easing : 'bounceOut' + }, + constructor : function(config) { + if (config) { + Ext.apply(this, config); + } + }, + //public + init : function (parent) { + + if(parent.displayInfo){ + this.parent = parent; + var ind = parent.items.indexOf(parent.displayItem); + parent.remove(parent.displayItem, true); + this.progressBar = new Ext.ProgressBar({ + text : this.defaultText, + width : this.progBarWidth, + animate : this.defaultAnimCfg + }); + + parent.displayItem = this.progressBar; + + parent.add(parent.displayItem); + parent.doLayout(); + Ext.apply(parent, this.parentOverrides); + + this.progressBar.on('render', function(pb) { + pb.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{ + ed.margins = pm('0 1 2'); + } + ed.setWidth(cm.getColumnWidth(i)); + ed.column = c; + if(ed.ownerCt !== this){ + ed.on('focus', this.ensureVisible, this); + ed.on('specialkey', this.onKey, this); + } + this.insert(i, ed); + } + this.initialized = true; + }, + + onKey: function(f, e){ + if(e.getKey() === e.ENTER){ + this.stopEditing(true); + e.stopPropagation(); + } + }, + + onGridKey: function(e){ + if(e.getKey() === e.ENTER && !this.isVisible()){ + var r = this.grid.getSelectionModel().getSelected(); + if(r){ + var index = this.grid.store.indexOf(r); + this.startEditing(index); + e.stopPropagation(); + } + } + }, + + ensureVisible: function(editor){ + if(this.isVisible()){ + this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true); + } + }, + + onRowClick: function(g, rowIndex, e){ + if(this.clicksToEdit == 'auto'){ + var li = this.lastClickIndex; + this.lastClickIndex = rowIndex; + if(li != rowIndex && !this.isVisible()){ + return; + } + } + this.startEditing(rowIndex, false); + this.doFocus.defer(this.focusDelay, this, [e.getPoint()]); + }, + + onRowDblClick: function(g, rowIndex, e){ + this.startEditing(rowIndex, false); + this.doFocus.defer(this.focusDelay, this, [e.getPoint()]); + }, + + onRender: function(){ + Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments); + this.el.swallowEvent(['keydown', 'keyup', 'keypress']); + this.btns = new Ext.Panel({ + baseCls: 'x-plain', + cls: 'x-btns', + elements:'body', + layout: 'table', + width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE + items: [{ + ref: 'saveBtn', + itemId: 'saveBtn', + xtype: 'button', + text: this.saveText, + 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); + }, + + 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.items.last().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();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ renderAll : function(ct, target) {
+ if(!this.innerCt){
+ // the innerCt prevents wrapping and shuffling while
+ // the container is resizing
+ this.innerCt = target.createChild({cls:'x-column-inner'});
+ this.innerCt.createChild({cls:'x-clear'});
+ }
+ Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+ // private
+ onLayout : function(ct, target){
+ var rs = ct.items.items,
+ len = rs.length,
+ r,
+ m,
+ i,
+ margins = [];
+
+ this.renderAll(ct, target);
+
+ var size = this.getLayoutTargetSize();
+
+ if(size.width < 1 && size.height < 1){ // display none?
+ return;
+ }
+
+ var h = size.height,
+ ph = h;
+
+ this.innerCt.setSize({height:h});
+
+ // some rows can be percentages while others are fixed
+ // so we need to make 2 passes
+
+ for(i = 0; i < len; i++){
+ r = rs[i];
+ m = r.getPositionEl().getMargins('tb');
+ margins[i] = m;
+ if(!r.rowHeight){
+ ph -= (r.getHeight() + m);
+ }
}
-
- // 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;
- });
+
+ 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.mon(pb.getEl().applyStyles('cursor:pointer'), 'click', this.handleProgressBarClick, this); - }, this, {single: true}); - + + 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, - 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); + + 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(), - 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); + + 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'); - saveText: 'Save', - cancelText: 'Cancel', - commitChangesText: 'You need to commit or cancel your changes', - errorText: 'Errors', +/** + * @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, - defaults: { - normalWidth: true - }, + constructor: function(config) { + var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass'); - 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' - ); - }, + var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig); - 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); - } - } + var plugins = config.plugins + ? (Ext.isArray(config.plugins) + ? config.plugins.push(spl) + : [config.plugins, spl]) + : spl; - // stopEditing without saving when a record is removed from Store. - grid.getStore().on('remove', function() { - this.stopEditing(false); - },this); + Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins})); + }, - 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, [])); + // private + getResizeEl: function(){ + return this.wrap; }, - beforedestroy: function() { - this.grid.getStore().un('remove', this.onStoreRemove, this); - this.stopEditing(false); - Ext.destroy(this.btns); + // private + getPositionEl: function(){ + return this.wrap; }, - refreshFields: function(){ - this.initFields(); - this.verifyLayout(); + // private + alignErrorIcon: function(){ + if (this.wrap) { + this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); + } }, - 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; + 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; }, - 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); + init: function(field){ + this.field = field; - 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); - } - } + 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); }, - 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); + 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" }); - 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(); + 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(); }, - slideHide : function(){ - this.hide(); + doAfterRender: function(){ + var y; + if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) { + this.el.position(); + this.el.setY(y); + } }, - 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(); - }else{ - ed = ed.field; - } - 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); + doEnable: function(){ + if (this.wrap) { + this.wrap.removeClass(this.field.disabledClass); } - this.initialized = true; }, - onKey: function(f, e){ - if(e.getKey() === e.ENTER){ - this.stopEditing(true); - e.stopPropagation(); + doDisable: function(){ + if (this.wrap) { + this.wrap.addClass(this.field.disabledClass); + this.el.removeClass(this.field.disabledClass); } }, - 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(); - } + doResize: function(w, h){ + if (typeof w == 'number') { + this.el.setWidth(w - this.trigger.getWidth()); } + this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth()); }, - ensureVisible: function(editor){ - if(this.isVisible()){ - this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true); + 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); } }, - onRowClick: function(g, rowIndex, e){ - if(this.clicksToEdit == 'auto'){ - var li = this.lastClickIndex; - this.lastClickIndex = rowIndex; - if(li != rowIndex && !this.isVisible()){ - return; - } + // private + checkTab: function(e){ + if (e.getKey() == e.TAB) { + this.triggerBlur(); } - 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()]); + // private + mimicBlur: function(e){ + if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) { + this.triggerBlur(); + } }, - 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]) - }] + // 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.btns.render(this.bwrap); + + 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); }, - afterRender: function(){ - Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments); - this.positionButtons(); - if(this.monitorValid){ - this.startMonitoring(); + 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); }, - onShow: function(){ - if(this.monitorValid){ - this.startMonitoring(); + //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")) { } - 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); + //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); }, - 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()); + //private + onMouseUp: function(){ + this.trigger.removeClass(this.tmpClickClass); + }, - this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2}); + //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 - preEditValue : function(r, field){ - var value = r.data[field]; - return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value; + //private + getMiddle: function(){ + var t = this.trigger.getTop(); + var h = this.trigger.getHeight(); + var middle = t + (h / 2); + return middle; }, - // private - postEditValue : function(value, originalValue, r, field){ - return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value; + //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; }, - doFocus: function(pt){ - if(this.isVisible()){ - var index = 0, - cm = this.grid.getColumnModel(), - c, - ed; - if(pt){ - index = this.getTargetColumnIndex(pt); - } - for(var i = index||0, len = cm.getColumnCount(); i < len; i++){ - c = cm.getColumnAt(i); - ed = c.getEditor(); - if(!c.hidden && ed){ - ed.field.focus(); - break; - } - } + handleMouseWheel: function(e){ + //disable scrolling when not focused + if (this.wrap.hasClass('x-trigger-wrap-focus') == false) { + return; } - }, - 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; - } - } + var delta = e.getWheelDelta(); + if (delta > 0) { + this.onSpinUp(); + e.stopEvent(); } - return match; + else + if (delta < 0) { + this.onSpinDown(); + e.stopEvent(); + } }, - startMonitoring : function(){ - if(!this.bound && this.monitorValid){ - this.bound = true; - Ext.TaskMgr.start({ - run : this.bindHandler, - interval : this.monitorPoll || 200, - scope: this - }); - } + //private + startDrag: function(){ + this.proxy.show(); + this._previousY = Ext.fly(this.dd.getDragEl()).getTop(); }, - stopMonitoring : function(){ - this.bound = false; - if(this.tooltip){ - this.tooltip.hide(); + //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; }, - isValid: function(){ - var valid = true; - this.items.each(function(f){ - if(!f.isValid(true)){ - valid = false; - return false; - } - }); - return valid; + //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 - bindHandler : function(){ - if(!this.bound){ - return false; // stops binding + //private + onSpinDown: function(){ + if (this.isSpinnable() == false) { + return; } - var valid = this.isValid(); - if(!valid && this.errorSummary){ - this.showTooltip(this.getErrorText().join('')); + if (Ext.EventObject.shiftKey == true) { + this.onSpinDownAlternate(); + return; } - this.btns.saveBtn.setDisabled(!valid); - this.fireEvent('validation', this, valid); + else { + this.spin(true, false); + } + this.field.fireEvent("spin", this); + this.field.fireEvent("spindown", this); }, - 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] - }); + //private + onSpinUpAlternate: function(){ + if (this.isSpinnable() == false) { + return; } - var v = this.grid.getView(), - top = parseInt(this.el.dom.style.top, 10), - scroll = v.scroller.dom.scrollTop, - h = this.el.getHeight(); + this.spin(false, true); + this.field.fireEvent("spin", this); + this.field.fireEvent("spinup", this); + }, - if(top + h >= scroll){ - t.initTarget(this.items.last().getEl()); - if(!t.rendered){ - t.show(); - t.hide(); - } - t.body.update(msg); - t.doAutoWidth(20); - t.show(); - }else if(t.rendered){ - t.hide(); + //private + onSpinDownAlternate: function(){ + if (this.isSpinnable() == false) { + return; } + this.spin(true, true); + this.field.fireEvent("spin", this); + this.field.fireEvent("spindown", this); }, - 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
- }]
-});
+ v = (isNaN(v)) ? this.defaultValue : v;
+ v = this.fixBoundries(v);
+ this.field.setRawValue(v);
+ },
-// 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,
+ fixBoundries: function(value){
+ var v = value;
- type: 'row',
+ 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;
+ }
- // private
- allowContainerRemove: false,
+ return this.fixPrecision(v);
+ },
// private
- isValidParent : function(c, target){
- return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ 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));
},
- getLayoutTargetSize : function() {
- var target = this.container.getLayoutTarget(), ret;
- if (target) {
- ret = target.getViewSize();
- ret.width -= target.getPadding('lr');
- ret.height -= target.getPadding('tb');
+ doDestroy: function(){
+ if (this.trigger) {
+ this.trigger.remove();
+ }
+ if (this.wrap) {
+ this.wrap.remove();
+ delete this.field.wrap;
}
- return ret;
- },
- renderAll : function(ct, target) {
- if(!this.innerCt){
- // the innerCt prevents wrapping and shuffling while
- // the container is resizing
- this.innerCt = target.createChild({cls:'x-column-inner'});
- this.innerCt.createChild({cls:'x-clear'});
+ if (this.splitter) {
+ this.splitter.remove();
}
- Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
- },
+
+ 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
- onLayout : function(ct, target){
- var rs = ct.items.items, len = rs.length, r, i;
+ animated : false,
- this.renderAll(ct, target);
+ createElements : function(){
+ var bd = Ext.getBody();
- var size = this.getLayoutTargetSize();
+ 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'});
- if(size.width < 1 && size.height < 1){ // display none?
+ 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
+ }
+ },
- var h = size.height,
- ph = h;
+ 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);
+ },
- this.innerCt.setSize({height:h});
+ doHide : function(){
+ this.active = false;
+ this.all.setDisplayed(false);
+ },
- // some rows can be percentages while others are fixed
- // so we need to make 2 passes
+ syncSize : function(){
+ this.applyBounds(false, false);
+ },
- for(i = 0; i < len; i++){
- r = rs[i];
- if(!r.rowHeight){
- ph -= (r.getHeight() + r.getEl().getMargins('tb'));
- }
- }
+ applyBounds : function(basePts, anim, doHide, callback, scope){
- ph = ph < 0 ? 0 : ph;
+ var rg = this.el.getRegion();
- for(i = 0; i < len; i++){
- r = rs[i];
- if(r.rowHeight){
- r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
- }
+ 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;
}
- // 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.layoutTargetSize = ts;
- this.onLayout(ct, target);
- }
+ 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]);
}
}
- delete this.adjustmentPass;
- }
+ },
- /**
- * @property activeItem
- * @hide
- */
-});
+ destroy : function(){
+ this.doHide();
+ Ext.destroy(
+ this.right,
+ this.left,
+ this.top,
+ this.bottom);
+ delete this.el;
+ delete this.all;
+ }
+};
-Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
-Ext.ns('Ext.ux.form');
-
-Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {
- initComponent : function(){
- Ext.ux.form.SearchField.superclass.initComponent.call(this);
- this.on('specialkey', function(f, e){
- if(e.getKey() == e.ENTER){
- this.onTrigger2Click();
- }
- }, this);
- },
-
- validationEvent:false,
- validateOnBlur:false,
- trigger1Class:'x-form-clear-trigger',
- trigger2Class:'x-form-search-trigger',
- hideTrigger1:true,
- width:180,
- hasSearch : false,
- paramName : 'query',
-
- onTrigger1Click : function(){
- if(this.hasSearch){
- this.el.dom.value = '';
- var o = {start: 0};
- this.store.baseParams = this.store.baseParams || {};
- this.store.baseParams[this.paramName] = '';
- this.store.reload({params:o});
- this.triggers[0].hide();
- this.hasSearch = false;
- }
- },
-
- onTrigger2Click : function(){
- var v = this.getRawValue();
- if(v.length < 1){
- this.onTrigger1Click();
- return;
- }
- var o = {start: 0};
- this.store.baseParams = this.store.baseParams || {};
- this.store.baseParams[this.paramName] = v;
- this.store.reload({params:o});
- this.hasSearch = true;
- this.triggers[0].show();
- }
-});Ext.ns('Ext.ux.form');
-
-/**
- * @class Ext.ux.form.SelectBox
- * @extends Ext.form.ComboBox
- * Makes a ComboBox more closely mimic an HTML SELECT. Supports clicking and dragging - * through the list, with item selection occurring when the mouse button is released. - * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable} - * on inner elements. Re-enabling editable after calling this will NOT work.
- * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392 - * @history 2007-07-08 jvs - * Slight mods for Ext 2.0 - * @xtype selectbox - */ -Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, { - constructor: function(config){ - this.searchResetDelay = 1000; - config = config || {}; - config = Ext.apply(config || {}, { - editable: false, - forceSelection: true, - rowHeight: false, - lastSearchTerm: false, - triggerAction: 'all', - mode: 'local' - }); - - Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments); - - this.lastSelectedIndex = this.selectedIndex || 0; - }, - - initEvents : function(){ - Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments); - // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE - this.el.on('keydown', this.keySearch, this, true); - this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this); - }, - - keySearch : function(e, target, options) { - var raw = e.getKey(); - var key = String.fromCharCode(raw); - var startIndex = 0; - - if( !this.store.getCount() ) { - return; - } - - switch(raw) { - case Ext.EventObject.HOME: - e.stopEvent(); - this.selectFirst(); - return; - - case Ext.EventObject.END: - e.stopEvent(); - this.selectLast(); - return; - - case Ext.EventObject.PAGEDOWN: - this.selectNextPage(); - e.stopEvent(); - return; - - case Ext.EventObject.PAGEUP: - this.selectPrevPage(); - e.stopEvent(); - return; - } - - // skip special keys other than the shift key - if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) { - return; - } - if( this.lastSearchTerm == key ) { - startIndex = this.lastSelectedIndex; - } - this.search(this.displayField, key, startIndex); - this.cshTask.delay(this.searchResetDelay); - }, - - onRender : function(ct, position) { - this.store.on('load', this.calcRowsPerPage, this); - Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments); - if( this.mode == 'local' ) { - this.initList(); - this.calcRowsPerPage(); - } - }, - - onSelect : function(record, index, skipCollapse){ - if(this.fireEvent('beforeselect', this, record, index) !== false){ - this.setValue(record.data[this.valueField || this.displayField]); - if( !skipCollapse ) { - this.collapse(); - } - this.lastSelectedIndex = index + 1; - this.fireEvent('select', this, record, index); - } - }, - - afterRender : function() { - Ext.ux.form.SelectBox.superclass.afterRender.apply(this, arguments); - if(Ext.isWebKit) { - this.el.swallowEvent('mousedown', true); - } - this.el.unselectable(); - this.innerList.unselectable(); - this.trigger.unselectable(); - this.innerList.on('mouseup', function(e, target, options) { - if( target.id && target.id == this.innerList.id ) { - return; - } - this.onViewClick(); - }, this); - - this.innerList.on('mouseover', function(e, target, options) { - if( target.id && target.id == this.innerList.id ) { - return; - } - this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1; - this.cshTask.delay(this.searchResetDelay); - }, this); - - this.trigger.un('click', this.onTriggerClick, this); - this.trigger.on('mousedown', function(e, target, options) { - e.preventDefault(); - this.onTriggerClick(); - }, this); - - this.on('collapse', function(e, target, options) { - Ext.getDoc().un('mouseup', this.collapseIf, this); - }, this, true); - - this.on('expand', function(e, target, options) { - Ext.getDoc().on('mouseup', this.collapseIf, this); - }, this, true); - }, - - clearSearchHistory : function() { - this.lastSelectedIndex = 0; - this.lastSearchTerm = false; - }, - - selectFirst : function() { - this.focusAndSelect(this.store.data.first()); - }, - - selectLast : function() { - this.focusAndSelect(this.store.data.last()); - }, - - selectPrevPage : function() { - if( !this.rowHeight ) { - return; - } - var index = Math.max(this.selectedIndex-this.rowsPerPage, 0); - this.focusAndSelect(this.store.getAt(index)); - }, - - selectNextPage : function() { - if( !this.rowHeight ) { - return; - } - var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1); - this.focusAndSelect(this.store.getAt(index)); - }, - - search : function(field, value, startIndex) { - field = field || this.displayField; - this.lastSearchTerm = value; - var index = this.store.find.apply(this.store, arguments); - if( index !== -1 ) { - this.focusAndSelect(index); - } - }, - - focusAndSelect : function(record) { - var index = Ext.isNumber(record) ? record : this.store.indexOf(record); - this.select(index, this.isExpanded()); - this.onSelect(this.store.getAt(index), index, this.isExpanded()); - }, - - calcRowsPerPage : function() { - if( this.store.getCount() ) { - this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight(); - this.rowsPerPage = this.maxHeight / this.rowHeight; - } else { - this.rowHeight = false; - } - } - -}); - -Ext.reg('selectbox', Ext.ux.form.SelectBox); - -//backwards compat -Ext.ux.SelectBox = Ext.ux.form.SelectBox; -/** - * @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){ - 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.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(idx + 1, slider); - pbar.on({ - change: function(pb, data){ - slider.setMaxValue(data.pages); - slider.setValue(data.activePage); - } - }); - } -});Ext.ns('Ext.ux.form'); - -/** - * @class Ext.ux.form.SpinnerField - * @extends Ext.form.NumberField - * Creates a field utilizing Ext.ux.Spinner - * @xtype spinnerfield - */ -Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, { - actionMode: 'wrap', - deferHeight: true, - autoSize: Ext.emptyFn, - onBlur: Ext.emptyFn, - adjustSize: Ext.BoxComponent.prototype.adjustSize, - - constructor: function(config) { - var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass'); - - var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig); - - var plugins = config.plugins - ? (Ext.isArray(config.plugins) - ? config.plugins.push(spl) - : [config.plugins, spl]) - : spl; - - Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins})); - }, - - // private - getResizeEl: function(){ - return this.wrap; - }, - - // private - getPositionEl: function(){ - return this.wrap; - }, - - // private - alignErrorIcon: function(){ - if (this.wrap) { - this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); - } - }, - - validateBlur: function(){ - return true; - } -}); - -Ext.reg('spinnerfield', Ext.ux.form.SpinnerField); - -//backwards compat -Ext.form.SpinnerField = Ext.ux.form.SpinnerField; -/** - * @class Ext.ux.Spinner - * @extends Ext.util.Observable - * Creates a Spinner control utilized by Ext.ux.form.SpinnerField - */ -Ext.ux.Spinner = Ext.extend(Ext.util.Observable, { - incrementValue: 1, - alternateIncrementValue: 5, - triggerClass: 'x-form-spinner-trigger', - splitterClass: 'x-form-spinner-splitter', - alternateKey: Ext.EventObject.shiftKey, - defaultValue: 0, - accelerate: false, - - constructor: function(config){ - Ext.ux.Spinner.superclass.constructor.call(this, config); - Ext.apply(this, config); - this.mimicing = false; - }, - - init: function(field){ - this.field = field; - - field.afterMethod('onRender', this.doRender, this); - field.afterMethod('onEnable', this.doEnable, this); - field.afterMethod('onDisable', this.doDisable, this); - field.afterMethod('afterRender', this.doAfterRender, this); - field.afterMethod('onResize', this.doResize, this); - field.afterMethod('onFocus', this.doFocus, this); - field.beforeMethod('onDestroy', this.doDestroy, this); - }, - - doRender: function(ct, position){ - var el = this.el = this.field.getEl(); - var f = this.field; - - if (!f.wrap) { - f.wrap = this.wrap = el.wrap({ - cls: "x-form-field-wrap" - }); - } - else { - this.wrap = f.wrap.addClass('x-form-field-wrap'); - } - - this.trigger = this.wrap.createChild({ - tag: "img", - src: Ext.BLANK_IMAGE_URL, - cls: "x-form-trigger " + this.triggerClass - }); - - if (!f.width) { - this.wrap.setWidth(el.getWidth() + this.trigger.getWidth()); - } - - this.splitter = this.wrap.createChild({ - tag: 'div', - cls: this.splitterClass, - style: 'width:13px; height:2px;' - }); - this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show(); - - this.proxy = this.trigger.createProxy('', this.splitter, true); - this.proxy.addClass("x-form-spinner-proxy"); - this.proxy.setStyle('left', '0px'); - this.proxy.setSize(14, 1); - this.proxy.hide(); - this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", { - dragElId: this.proxy.id - }); - - this.initTrigger(); - this.initSpinner(); - }, - - doAfterRender: function(){ - var y; - if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) { - this.el.position(); - this.el.setY(y); - } - }, - - doEnable: function(){ - if (this.wrap) { - this.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(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 +//backwards compat Ext.Spotlight = Ext.ux.Spotlight;/** * @class Ext.ux.StatusBar *Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to @@ -8854,59 +8931,150 @@ statusBar.setStatus({ } }); Ext.reg('statusbar', Ext.ux.StatusBar); -/** - * @class Ext.ux.TabCloseMenu - * @extends Object - * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs. - * - * @ptype tabclosemenu - */ -Ext.ux.TabCloseMenu = function(){ - var tabs, menu, ctxItem; - this.init = function(tp){ - tabs = tp; - tabs.on('contextmenu', onContextMenu); - }; - - function onContextMenu(ts, item, e){ - if(!menu){ // create context menu on first right click - menu = new Ext.menu.Menu({ - items: [{ - id: tabs.id + '-close', - text: 'Close Tab', - handler : function(){ - tabs.remove(ctxItem); - } - },{ - id: tabs.id + '-close-others', - text: 'Close Other Tabs', - handler : function(){ - tabs.items.each(function(item){ - if(item.closable && item != ctxItem){ - tabs.remove(item); - } - }); - } - }]}); - } - ctxItem = item; - var items = menu.items; - items.get(tabs.id + '-close').setDisabled(!item.closable); - var disableOthers = true; - tabs.items.each(function(){ - if(this != item && this.closable){ - disableOthers = false; - return false; - } - }); - items.get(tabs.id + '-close-others').setDisabled(disableOthers); - e.stopEvent(); - menu.showAt(e.getPoint()); - } -}; - -Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu); -Ext.ns('Ext.ux.grid'); +/** + * @class Ext.ux.TabCloseMenu + * @extends Object + * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs. Note that the menu respects + * the closable configuration on the tab. As such, commands like remove others and remove all will not + * remove items that are not closable. + * + * @constructor + * @param {Object} config The configuration options + * @ptype tabclosemenu + */ +Ext.ux.TabCloseMenu = Ext.extend(Object, { + /** + * @cfg {String} closeTabText + * The text for closing the current tab. Defaults to 'Close Tab'. + */ + closeTabText: 'Close Tab', + + /** + * @cfg {String} closeOtherTabsText + * The text for closing all tabs except the current one. Defaults to 'Close Other Tabs'. + */ + closeOtherTabsText: 'Close Other Tabs', + + /** + * @cfg {Boolean} showCloseAll + * Indicates whether to show the 'Close All' option. Defaults to true. + */ + showCloseAll: true, + + /** + * @cfg {String} closeAllTabsText + *
The text for closing all tabs. Defaults to 'Close All Tabs'. + */ + closeAllTabsText: 'Close All Tabs', + + constructor : function(config){ + Ext.apply(this, config || {}); + }, + + //public + init : function(tabs){ + this.tabs = tabs; + tabs.on({ + scope: this, + contextmenu: this.onContextMenu, + destroy: this.destroy + }); + }, + + destroy : function(){ + Ext.destroy(this.menu); + delete this.menu; + delete this.tabs; + delete this.active; + }, + + // private + onContextMenu : function(tabs, item, e){ + this.active = item; + var m = this.createMenu(), + disableAll = true, + disableOthers = true, + closeAll = m.getComponent('closeall'); + + m.getComponent('close').setDisabled(!item.closable); + tabs.items.each(function(){ + if(this.closable){ + disableAll = false; + if(this != item){ + disableOthers = false; + return false; + } + } + }); + m.getComponent('closeothers').setDisabled(disableOthers); + if(closeAll){ + closeAll.setDisabled(disableAll); + } + + e.stopEvent(); + m.showAt(e.getPoint()); + }, + + createMenu : function(){ + if(!this.menu){ + var items = [{ + itemId: 'close', + text: this.closeTabText, + scope: this, + handler: this.onClose + }]; + if(this.showCloseAll){ + items.push('-'); + } + items.push({ + itemId: 'closeothers', + text: this.closeOtherTabsText, + scope: this, + handler: this.onCloseOthers + }); + if(this.showCloseAll){ + items.push({ + itemId: 'closeall', + text: this.closeAllTabsText, + scope: this, + handler: this.onCloseAll + }); + } + this.menu = new Ext.menu.Menu({ + items: items + }); + } + return this.menu; + }, + + onClose : function(){ + this.tabs.remove(this.active); + }, + + onCloseOthers : function(){ + this.doClose(true); + }, + + onCloseAll : function(){ + this.doClose(false); + }, + + doClose : function(excludeActive){ + var items = []; + this.tabs.items.each(function(item){ + if(item.closable){ + if(!excludeActive || item != this.active){ + items.push(item); + } + } + }, this); + Ext.each(items, function(item){ + this.tabs.remove(item); + }, this); + } +}); + +Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);Ext.ns('Ext.ux.grid'); /** * @class Ext.ux.grid.TableGrid @@ -9495,31 +9663,25 @@ Ext.ux.ValidationStatus = Ext.extend(Ext.Component, { this.showErrors(); } } -});(function() { +});(function() { Ext.override(Ext.list.Column, { - init : function() { - if(!this.type){ - this.type = "auto"; + 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; } - var st = Ext.data.SortTypes; // named sortTypes are supported, here we look them up - if(typeof this.sortType == "string"){ - this.sortType = st[this.sortType]; - } - - // set default sortType for strings and dates - if(!this.sortType){ - switch(this.type){ - case "string": - this.sortType = st.asUCString; - break; - case "date": - this.sortType = st.asDate; - break; - default: - this.sortType = st.none; - } + if(Ext.isString(st)){ + this.sortType = Ext.data.SortTypes[st]; + }else if(Ext.isEmpty(st)){ + this.sortType = this.type.sortType; } } });