X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6:/examples/ux/gridfilters/GridFilters.js..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/examples/ux/grid/FiltersFeature.js diff --git a/examples/ux/gridfilters/GridFilters.js b/examples/ux/grid/FiltersFeature.js similarity index 54% rename from examples/ux/gridfilters/GridFilters.js rename to examples/ux/grid/FiltersFeature.js index 901b0d1c..ad27e6ef 100644 --- a/examples/ux/gridfilters/GridFilters.js +++ b/examples/ux/grid/FiltersFeature.js @@ -1,125 +1,116 @@ -/*! - * Ext JS Library 3.3.1 - * Copyright(c) 2006-2010 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -Ext.namespace('Ext.ux.grid'); - /** - * @class Ext.ux.grid.GridFilters - * @extends Ext.util.Observable - *

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

- *

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

- *

Features:

- *
- *

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({
+ * @class Ext.ux.grid.FiltersFeature
+ * @extends Ext.grid.Feature
+
+FiltersFeature is a grid {@link Ext.grid.Feature feature} that allows for a slightly more
+robust representation of filtering than what is provided by the default store.
+
+Filtering is adjusted by the user using the grid's column header menu (this menu can be
+disabled through configuration). Through this menu users can configure, enable, and
+disable filters for each column.
+
+#Features#
+
+##Filtering implementations:##
+
+Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can be backed by a
+{@link Ext.data.Store}), and Boolean. Additional custom filter types and menus are easily
+created by extending {@link Ext.ux.grid.filter.Filter}.
+
+##Graphical Indicators:##
+
+Columns that are filtered have {@link #filterCls a configurable css class} applied to the column headers.
+
+##Automatic Reconfiguration:##
+
+Filters automatically reconfigure when the grid 'reconfigure' event fires.
+
+##Stateful:##
+
+Filter information will be persisted across page loads by specifying a `stateId`
+in the Grid configuration.
+
+The filter collection binds to the {@link Ext.grid.Panel#beforestaterestore beforestaterestore}
+and {@link Ext.grid.Panel#beforestatesave beforestatesave} events in order to be stateful.
+
+##GridPanel Changes:##
+
+- A `filters` property is added to the GridPanel using this feature.
+- A `filterupdate` event is added to the GridPanel and is fired upon onStateChange completion.
+
+##Server side code examples:##
+
+- [PHP](http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php) - (Thanks VinylFox)
+- [Ruby on Rails](http://extjs.com/forum/showthread.php?p=77326#post77326) - (Thanks Zyclops)
+- [Ruby on Rails](http://extjs.com/forum/showthread.php?p=176596#post176596) - (Thanks Rotomaul)
+- [Python](http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/) - (Thanks Matt)
+- [Grails](http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/) - (Thanks Mike)
+
+#Example usage:#
+
+    var store = Ext.create('Ext.data.Store', {
+        pageSize: 15
+        ...
+    });
+
+    var filtersCfg = {
+        ftype: 'filters',
+        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 headers 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 grid = Ext.create('Ext.grid.Panel', {
          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
- * 
+ columns: ..., + filters: [filtersCfg], + height: 400, + width: 700, + bbar: Ext.create('Ext.PagingToolbar', { + store: store + }) + }); + + // a filters property is added to the GridPanel + grid.filters + + * @markdown */ -Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, { +Ext.define('Ext.ux.grid.FiltersFeature', { + extend: 'Ext.grid.feature.Feature', + alias: 'feature.filters', + uses: [ + 'Ext.ux.grid.menu.ListMenu', + 'Ext.ux.grid.menu.RangeMenu', + 'Ext.ux.grid.filter.BooleanFilter', + 'Ext.ux.grid.filter.DateFilter', + 'Ext.ux.grid.filter.ListFilter', + 'Ext.ux.grid.filter.NumericFilter', + 'Ext.ux.grid.filter.StringFilter' + ], + /** * @cfg {Boolean} autoReload * Defaults to true, reloading the datasource when a filter change happens. @@ -179,57 +170,128 @@ Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, { */ updateBuffer : 500, + // doesn't handle grid body events + hasFeatureEvent: false, + + /** @private */ constructor : function (config) { + var me = this; + config = config || {}; - this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this); - this.filters = new Ext.util.MixedCollection(); - this.filters.getKey = function (o) { + Ext.apply(me, config); + + me.deferredUpdate = Ext.create('Ext.util.DelayedTask', me.reload, me); + + // Init filters + me.filters = Ext.create('Ext.util.MixedCollection', false, function (o) { return o ? o.dataIndex : null; - }; - this.addFilters(config.filters); - delete config.filters; - Ext.apply(this, config); + }); + me.filterConfigs = config.filters; }, - /** @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; + attachEvents: function() { + var me = this, + view = me.view, + headerCt = view.headerCt, + grid = me.getGridPanel(); - this.grid.addEvents({'filterupdate': true}); + me.bindStore(view.getStore(), true); - grid.on({ - scope: this, - beforestaterestore: this.applyState, - beforestatesave: this.saveState, - beforedestroy: this.destroy, - reconfigure: this.onReconfigure - }); + // Listen for header menu being created + headerCt.on('menucreate', me.onMenuCreate, me); - if (grid.rendered){ - this.onRender(); - } else { - grid.on({ - scope: this, - single: true, - render: this.onRender - }); + view.on('refresh', me.onRefresh, me); + grid.on({ + scope: me, + beforestaterestore: me.applyState, + beforestatesave: me.saveState, + beforedestroy: me.destroy + }); + + // Add event and filters shortcut on grid panel + grid.filters = me; + grid.addEvents('filterupdate'); + }, + + /** + * @private Create the Filter objects for the current configuration, destroying any existing ones first. + */ + createFilters: function() { + var me = this, + filterConfigs = me.filterConfigs, + hadFilters = me.filters.getCount(), + state; + if (hadFilters) { + state = {}; + me.saveState(null, state); + } + me.removeAll(); + me.addFilters(Ext.isEmpty(filterConfigs) ? me.view.headerCt.items.items : filterConfigs); + if (hadFilters) { + me.applyState(null, state); + } + }, + + /** + * @private Handle creation of the grid's header menu. Initializes the filters and listens + * for the menu being shown. + */ + onMenuCreate: function(headerCt, menu) { + var me = this; + me.createFilters(); + menu.on('beforeshow', me.onMenuBeforeShow, me); + }, + + /** + * @private Handle showing of the grid's header menu. Sets up the filter item and menu + * appropriate for the target column. + */ + onMenuBeforeShow: function(menu) { + var me = this, + menuItem, filter; + + if (me.showMenu) { + menuItem = me.menuItem; + if (!menuItem || menuItem.isDestroyed) { + me.createMenuItem(menu); + menuItem = me.menuItem; } - } else if (grid instanceof Ext.PagingToolbar) { - this.toolbar = grid; + filter = me.getMenuFilter(); + + if (filter) { + menuItem.menu = filter.menu; + menuItem.setChecked(filter.active); + // disable the menu if filter.disabled explicitly set to true + menuItem.setDisabled(filter.disabled === true); + } + menuItem.setVisible(!!filter); + this.sep.setVisible(!!filter); } }, + + createMenuItem: function(menu) { + var me = this; + me.sep = menu.add('-'); + me.menuItem = menu.add({ + checked: false, + itemId: 'filters', + text: me.menuFilterText, + listeners: { + scope: me, + checkchange: me.onCheckChange, + beforecheckchange: me.onBeforeCheck + } + }); + }, + + getGridPanel: function() { + return this.view.up('gridpanel'); + }, + /** * @private * Handler for the grid's beforestaterestore event (fires before the state of the @@ -274,28 +336,15 @@ Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, { 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; - } + var me = this; + Ext.destroyMembers(me, 'menuItem', 'sep'); + me.removeAll(); + me.clearListeners(); }, /** @@ -332,81 +381,14 @@ Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, { 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); + var header = this.view.headerCt.getMenu().activeHeader; + return header ? this.filters.get(header.dataIndex) : null; }, /** @private */ @@ -426,23 +408,24 @@ TODO: lazy rendering * @param {Object} filter Standard signature of the event before the event is fired */ onStateChange : function (event, filter) { - if (event === 'serialize') { - return; - } + if (event !== 'serialize') { + var me = this, + grid = me.getGridPanel(); - if (filter == this.getMenuFilter()) { - this.menu.setChecked(filter.active, false); - } + if (filter == me.getMenuFilter()) { + me.menuItem.setChecked(filter.active, false); + } - if ((this.autoReload || this.local) && !this.applyingState) { - this.deferredUpdate.delay(this.updateBuffer); - } - this.updateColumnHeadings(); + if ((me.autoReload || me.local) && !me.applyingState) { + me.deferredUpdate.delay(me.updateBuffer); + } + me.updateColumnHeadings(); - if (!this.applyingState) { - this.grid.saveState(); + if (!me.applyingState) { + grid.saveState(); + } + grid.fireEvent('filterupdate', me, filter); } - this.grid.fireEvent('filterupdate', this, filter); }, /** @@ -480,32 +463,28 @@ TODO: lazy rendering * 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); - } + var me = this, + headerCt = me.view.headerCt; + if (headerCt) { + headerCt.items.each(function(header) { + var filter = me.getFilter(header.dataIndex); + header[filter && filter.active ? 'addCls' : 'removeCls'](me.filterCls); + }); } }, /** @private */ reload : function () { - if (this.local) { - this.grid.store.clearFilter(true); - this.grid.store.filterBy(this.getRecordFilter()); + var me = this, + store = me.view.getStore(), + start; + + if (me.local) { + store.clearFilter(true); + store.filterBy(me.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(); + me.deferredUpdate.cancel(); + store.loadPage(1); } }, @@ -549,34 +528,13 @@ TODO: lazy rendering /** * 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. + * @param {Array} filters An Array of filter configuration objects. */ addFilters : function (filters) { if (filters) { - var i, len, filter, cm = false, dI; - if (filters instanceof Ext.grid.ColumnModel) { - filters = filters.config; - cm = true; - } + var i, len, filter; for (i = 0, len = filters.length; i < len; i++) { - filter = false; - if (cm) { - dI = filters[i].dataIndex; - filter = filters[i].filter || filters[i].filterable; - if (filter){ - filter = (filter === true) ? {} : filter; - Ext.apply(filter, {dataIndex:dI}); - // filter type is specified in order of preference: - // filter type specified in config - // type specified in store's field's type config - filter.type = filter.type || this.store.fields.get(dI).type.type; - } - } else { - filter = filters[i]; - } + filter = filters[i]; // if filter config found add filter for the column if (filter) { this.addFilter(filter); @@ -685,7 +643,7 @@ filters[0][data][value]="someValue3"& } // only build if there is active filter if (tmp.length > 0){ - p[this.paramPrefix] = Ext.util.JSON.encode(tmp); + p[this.paramPrefix] = Ext.JSON.encode(tmp); } } return p; @@ -732,9 +690,6 @@ filters[0][data][value]="someValue3"& type = 'boolean'; break; } - return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter']; + return Ext.ClassManager.getByAlias('gridfilter.' + type); } }); - -// register ptype -Ext.preg('gridfilters', Ext.ux.grid.GridFilters);