3 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
4 <title>The source code</title>
5 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6 <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8 <body onload="prettyPrint();">
9 <pre class="prettyprint lang-js">/*!
10 * Ext JS Library 3.3.1
11 * Copyright(c) 2006-2010 Sencha Inc.
12 * licensing@sencha.com
13 * http://www.sencha.com/license
15 Ext.namespace('Ext.ux.grid');
17 <div id="cls-Ext.ux.grid.GridFilters"></div>/**
18 * @class Ext.ux.grid.GridFilters
19 * @extends Ext.util.Observable
20 * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that
21 * allow for a slightly more robust representation of filtering than what is
22 * provided by the default store.</p>
23 * <p>Filtering is adjusted by the user using the grid's column header menu
24 * (this menu can be disabled through configuration). Through this menu users
25 * can configure, enable, and disable filters for each column.</p>
26 * <p><b><u>Features:</u></b></p>
27 * <div class="mdetail-params"><ul>
28 * <li><b>Filtering implementations</b> :
29 * <div class="sub-desc">
30 * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can
31 * be backed by a Ext.data.Store), and Boolean. Additional custom filter types
32 * and menus are easily created by extending Ext.ux.grid.filter.Filter.
34 * <li><b>Graphical indicators</b> :
35 * <div class="sub-desc">
36 * Columns that are filtered have {@link #filterCls a configurable css class}
37 * applied to the column headers.
40 * <div class="sub-desc">
41 * If specified as a plugin to the grid's configured PagingToolbar, the current page
42 * will be reset to page 1 whenever you update the filters.
44 * <li><b>Automatic Reconfiguration</b> :
45 * <div class="sub-desc">
46 * Filters automatically reconfigure when the grid 'reconfigure' event fires.
48 * <li><b>Stateful</b> :
49 * Filter information will be persisted across page loads by specifying a
50 * <code>stateId</code> in the Grid configuration.
51 * <div class="sub-desc">
52 * The filter collection binds to the
53 * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code>
54 * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code>
55 * events in order to be stateful.
57 * <li><b>Grid Changes</b> :
58 * <div class="sub-desc"><ul>
59 * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to
61 * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is
62 * fired upon onStateChange completion.</li>
64 * <li><b>Server side code examples</b> :
65 * <div class="sub-desc"><ul>
66 * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li>
67 * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li>
68 * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li>
69 * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li>
70 * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li>
73 * <p><b><u>Example usage:</u></b></p>
75 var store = new Ext.data.GroupingStore({
79 var filters = new Ext.ux.grid.GridFilters({
80 autoReload: false, //don't reload automatically
81 local: true, //only filter locally
82 // filters may be configured through the plugin,
83 // or in the column definition within the column model configuration
95 dataIndex: 'dateAdded'
99 options: ['extra small', 'small', 'medium', 'large', 'extra large'],
106 var cm = new Ext.grid.ColumnModel([{
110 var grid = new Ext.grid.GridPanel({
113 view: new Ext.grid.GroupingView(),
117 bbar: new Ext.PagingToolbar({
120 plugins: [filters] //reset page to page 1 if filters change
124 store.load({params: {start: 0, limit: 15}});
126 // a filters property is added to the grid
130 Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {
131 <div id="cfg-Ext.ux.grid.GridFilters-autoReload"></div>/**
132 * @cfg {Boolean} autoReload
133 * Defaults to true, reloading the datasource when a filter change happens.
134 * Set this to false to prevent the datastore from being reloaded if there
135 * are changes to the filters. See <code>{@link updateBuffer}</code>.
138 <div id="cfg-Ext.ux.grid.GridFilters-encode"></div>/**
139 * @cfg {Boolean} encode
140 * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to
141 * encode the filter query parameter sent with a remote request.
144 <div id="cfg-Ext.ux.grid.GridFilters-filters"></div>/**
145 * @cfg {Array} filters
146 * An Array of filters config objects. Refer to each filter type class for
147 * configuration details specific to each filter type. Filters for Strings,
148 * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters
151 <div id="cfg-Ext.ux.grid.GridFilters-filterCls"></div>/**
152 * @cfg {String} filterCls
153 * The css class to be applied to column headers with active filters.
154 * Defaults to <tt>'ux-filterd-column'</tt>.
156 filterCls : 'ux-filtered-column',
157 <div id="cfg-Ext.ux.grid.GridFilters-local"></div>/**
158 * @cfg {Boolean} local
159 * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)
160 * instead of the default (<tt>false</tt>) server side filtering.
163 <div id="cfg-Ext.ux.grid.GridFilters-menuFilterText"></div>/**
164 * @cfg {String} menuFilterText
165 * defaults to <tt>'Filters'</tt>.
167 menuFilterText : 'Filters',
168 <div id="cfg-Ext.ux.grid.GridFilters-paramPrefix"></div>/**
169 * @cfg {String} paramPrefix
170 * The url parameter prefix for the filters.
171 * Defaults to <tt>'filter'</tt>.
173 paramPrefix : 'filter',
174 <div id="cfg-Ext.ux.grid.GridFilters-showMenu"></div>/**
175 * @cfg {Boolean} showMenu
176 * Defaults to true, including a filter submenu in the default header menu.
179 <div id="cfg-Ext.ux.grid.GridFilters-stateId"></div>/**
180 * @cfg {String} stateId
181 * Name of the value to be used to store state information.
184 <div id="cfg-Ext.ux.grid.GridFilters-updateBuffer"></div>/**
185 * @cfg {Integer} updateBuffer
186 * Number of milliseconds to defer store updates since the last filter change.
191 constructor : function (config) {
192 config = config || {};
193 this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);
194 this.filters = new Ext.util.MixedCollection();
195 this.filters.getKey = function (o) {
196 return o ? o.dataIndex : null;
198 this.addFilters(config.filters);
199 delete config.filters;
200 Ext.apply(this, config);
204 init : function (grid) {
205 if (grid instanceof Ext.grid.GridPanel) {
208 this.bindStore(this.grid.getStore(), true);
209 // assumes no filters were passed in the constructor, so try and use ones from the colModel
210 if(this.filters.getCount() == 0){
211 this.addFilters(this.grid.getColumnModel());
214 this.grid.filters = this;
216 this.grid.addEvents({'filterupdate': true});
220 beforestaterestore: this.applyState,
221 beforestatesave: this.saveState,
222 beforedestroy: this.destroy,
223 reconfigure: this.onReconfigure
232 render: this.onRender
236 } else if (grid instanceof Ext.PagingToolbar) {
243 * Handler for the grid's beforestaterestore event (fires before the state of the
245 * @param {Object} grid The grid object
246 * @param {Object} state The hash of state values returned from the StateProvider.
248 applyState : function (grid, state) {
250 this.applyingState = true;
253 for (key in state.filters) {
254 filter = this.filters.get(key);
256 filter.setValue(state.filters[key]);
257 filter.setActive(true);
261 this.deferredUpdate.cancel();
265 delete this.applyingState;
266 delete state.filters;
269 <div id="method-Ext.ux.grid.GridFilters-saveState"></div>/**
270 * Saves the state of all active filters
271 * @param {Object} grid
272 * @param {Object} state
275 saveState : function (grid, state) {
277 this.filters.each(function (filter) {
279 filters[filter.dataIndex] = filter.getValue();
282 return (state.filters = filters);
287 * Handler called when the grid is rendered
289 onRender : function () {
290 this.grid.getView().on('refresh', this.onRefresh, this);
296 * Handler called by the grid 'beforedestroy' event
298 destroy : function () {
300 this.purgeListeners();
303 Ext.menu.MenuMgr.unregister(this.filterMenu);
304 this.filterMenu.destroy();
305 this.filterMenu = this.menu.menu = null;
309 <div id="method-Ext.ux.grid.GridFilters-removeAll"></div>/**
310 * Remove all filters, permanently destroying them.
312 removeAll : function () {
314 Ext.destroy.apply(Ext, this.filters.items);
315 // remove all items from the collection
316 this.filters.clear();
321 <div id="method-Ext.ux.grid.GridFilters-bindStore"></div>/**
322 * Changes the data store bound to this view and refreshes it.
323 * @param {Store} store The store to bind to this view
325 bindStore : function(store, initial){
326 if(!initial && this.store){
328 store.un('load', this.onLoad, this);
330 store.un('beforeload', this.onBeforeLoad, this);
335 store.on('load', this.onLoad, this);
337 store.on('beforeload', this.onBeforeLoad, this);
345 * Handler called when the grid reconfigure event fires
347 onReconfigure : function () {
348 this.bindStore(this.grid.getStore());
349 this.store.clearFilter();
351 this.addFilters(this.grid.getColumnModel());
352 this.updateColumnHeadings();
355 createMenu : function () {
356 var view = this.grid.getView(),
359 if (this.showMenu && hmenu) {
361 this.sep = hmenu.addSeparator();
362 this.filterMenu = new Ext.menu.Menu({
363 id: this.grid.id + '-filters-menu'
365 this.menu = hmenu.add({
368 text: this.menuFilterText,
369 menu: this.filterMenu
374 checkchange: this.onCheckChange,
375 beforecheckchange: this.onBeforeCheck
377 hmenu.on('beforeshow', this.onMenu, this);
379 this.updateColumnHeadings();
384 * Get the filter menu from the filters MixedCollection based on the clicked header
386 getMenuFilter : function () {
387 var view = this.grid.getView();
388 if (!view || view.hdCtxIndex === undefined) {
391 return this.filters.get(
392 view.cm.config[view.hdCtxIndex].dataIndex
398 * Handler called by the grid's hmenu beforeshow event
400 onMenu : function (filterMenu) {
401 var filter = this.getMenuFilter();
407 filter.menu = filter.createMenu();
410 this.menu.menu = filter.menu;
411 this.menu.setChecked(filter.active, false);
412 // disable the menu if filter.disabled explicitly set to true
413 this.menu.setDisabled(filter.disabled === true);
416 this.menu.setVisible(filter !== undefined);
417 this.sep.setVisible(filter !== undefined);
421 onCheckChange : function (item, value) {
422 this.getMenuFilter().setActive(value);
426 onBeforeCheck : function (check, value) {
427 return !value || this.getMenuFilter().isActivatable();
432 * Handler for all events on filters.
433 * @param {String} event Event name
434 * @param {Object} filter Standard signature of the event before the event is fired
436 onStateChange : function (event, filter) {
437 if (event === 'serialize') {
441 if (filter == this.getMenuFilter()) {
442 this.menu.setChecked(filter.active, false);
445 if ((this.autoReload || this.local) && !this.applyingState) {
446 this.deferredUpdate.delay(this.updateBuffer);
448 this.updateColumnHeadings();
450 if (!this.applyingState) {
451 this.grid.saveState();
453 this.grid.fireEvent('filterupdate', this, filter);
458 * Handler for store's beforeload event when configured for remote filtering
459 * @param {Object} store
460 * @param {Object} options
462 onBeforeLoad : function (store, options) {
463 options.params = options.params || {};
464 this.cleanParams(options.params);
465 var params = this.buildQuery(this.getFilterData());
466 Ext.apply(options.params, params);
471 * Handler for store's load event when configured for local filtering
472 * @param {Object} store
473 * @param {Object} options
475 onLoad : function (store, options) {
476 store.filterBy(this.getRecordFilter());
481 * Handler called when the grid's view is refreshed
483 onRefresh : function () {
484 this.updateColumnHeadings();
487 <div id="method-Ext.ux.grid.GridFilters-updateColumnHeadings"></div>/**
488 * Update the styles for the header row based on the active filters
490 updateColumnHeadings : function () {
491 var view = this.grid.getView(),
494 for (i = 0, len = view.cm.config.length; i < len; i++) {
495 filter = this.getFilter(view.cm.config[i].dataIndex);
496 Ext.fly(view.getHeaderCell(i))[filter && filter.active ? 'addClass' : 'removeClass'](this.filterCls);
502 reload : function () {
504 this.grid.store.clearFilter(true);
505 this.grid.store.filterBy(this.getRecordFilter());
508 store = this.grid.store;
509 this.deferredUpdate.cancel();
511 start = store.paramNames.start;
512 if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {
513 store.lastOptions.params[start] = 0;
521 * Method factory that generates a record validator for the filters active at the time
525 getRecordFilter : function () {
527 this.filters.each(function (filter) {
534 return function (record) {
535 for (i = 0; i < len; i++) {
536 if (!f[i].validateRecord(record)) {
544 <div id="method-Ext.ux.grid.GridFilters-addFilter"></div>/**
545 * Adds a filter to the collection and observes it for state change.
546 * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.
547 * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.
549 addFilter : function (config) {
550 var Cls = this.getFilterClass(config.type),
551 filter = config.menu ? config : (new Cls(config));
552 this.filters.add(filter);
554 Ext.util.Observable.capture(filter, this.onStateChange, this);
558 <div id="method-Ext.ux.grid.GridFilters-addFilters"></div>/**
559 * Adds filters to the collection.
560 * @param {Array/Ext.grid.ColumnModel} filters Either an Array of
561 * filter configuration objects or an Ext.grid.ColumnModel. The columns
562 * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>
563 * property and, if present, will be used as the filter configuration object.
565 addFilters : function (filters) {
567 var i, len, filter, cm = false, dI;
568 if (filters instanceof Ext.grid.ColumnModel) {
569 filters = filters.config;
572 for (i = 0, len = filters.length; i < len; i++) {
575 dI = filters[i].dataIndex;
576 filter = filters[i].filter || filters[i].filterable;
578 filter = (filter === true) ? {} : filter;
579 Ext.apply(filter, {dataIndex:dI});
580 // filter type is specified in order of preference:
581 // filter type specified in config
582 // type specified in store's field's type config
583 filter.type = filter.type || this.store.fields.get(dI).type.type;
588 // if filter config found add filter for the column
590 this.addFilter(filter);
596 <div id="method-Ext.ux.grid.GridFilters-getFilter"></div>/**
597 * Returns a filter for the given dataIndex, if one exists.
598 * @param {String} dataIndex The dataIndex of the desired filter object.
599 * @return {Ext.ux.grid.filter.Filter}
601 getFilter : function (dataIndex) {
602 return this.filters.get(dataIndex);
605 <div id="method-Ext.ux.grid.GridFilters-clearFilters"></div>/**
606 * Turns all filters off. This does not clear the configuration information
607 * (see {@link #removeAll}).
609 clearFilters : function () {
610 this.filters.each(function (filter) {
611 filter.setActive(false);
615 <div id="method-Ext.ux.grid.GridFilters-getFilterData"></div>/**
616 * Returns an Array of the currently active filters.
617 * @return {Array} filters Array of the currently active filters.
619 getFilterData : function () {
620 var filters = [], i, len;
622 this.filters.each(function (f) {
624 var d = [].concat(f.serialize());
625 for (i = 0, len = d.length; i < len; i++) {
636 <div id="method-Ext.ux.grid.GridFilters-buildQuery"></div>/**
637 * Function to take the active filters data and build it into a query.
638 * The format of the query depends on the <code>{@link #encode}</code>
640 * <div class="mdetail-params"><ul>
642 * <li><b><tt>false</tt></b> : <i>Default</i>
643 * <div class="sub-desc">
644 * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:
646 filters[0][field]="someDataIndex"&
647 filters[0][data][comparison]="someValue1"&
648 filters[0][data][type]="someValue2"&
649 filters[0][data][value]="someValue3"&
652 * <li><b><tt>true</tt></b> :
653 * <div class="sub-desc">
654 * JSON encode the filter data
656 filters[0][field]="someDataIndex"&
657 filters[0][data][comparison]="someValue1"&
658 filters[0][data][type]="someValue2"&
659 filters[0][data][value]="someValue3"&
663 * Override this method to customize the format of the filter query for remote requests.
664 * @param {Array} filters A collection of objects representing active filters and their configuration.
665 * Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured
666 * to be unique as any one filter may be a composite of more basic filters for the same dataIndex.
667 * @return {Object} Query keys and values
669 buildQuery : function (filters) {
670 var p = {}, i, f, root, dataPrefix, key, tmp,
671 len = filters.length;
674 for (i = 0; i < len; i++) {
676 root = [this.paramPrefix, '[', i, ']'].join('');
677 p[root + '[field]'] = f.field;
679 dataPrefix = root + '[data]';
680 for (key in f.data) {
681 p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];
686 for (i = 0; i < len; i++) {
694 // only build if there is active filter
696 p[this.paramPrefix] = Ext.util.JSON.encode(tmp);
702 <div id="method-Ext.ux.grid.GridFilters-cleanParams"></div>/**
703 * Removes filter related query parameters from the provided object.
704 * @param {Object} p Query parameters that may contain filter related fields.
706 cleanParams : function (p) {
707 // if encoding just delete the property
709 delete p[this.paramPrefix];
710 // otherwise scrub the object of filter data
713 regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');
715 if (regex.test(key)) {
722 <div id="method-Ext.ux.grid.GridFilters-getFilterClass"></div>/**
723 * Function for locating filter classes, overwrite this with your favorite
724 * loader to provide dynamic filter loading.
725 * @param {String} type The type of filter to load ('Filter' is automatically
726 * appended to the passed type; eg, 'string' becomes 'StringFilter').
727 * @return {Class} The Ext.ux.grid.filter.Class
729 getFilterClass : function (type) {
730 // map the supported Ext.data.Field type values into a supported filter
743 return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];
748 Ext.preg('gridfilters', Ext.ux.grid.GridFilters);