make sure the README will appear on github
[extjs.git] / examples / grid-filtering / grid / GridFilters.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 Ext.grid.GridFilters = function(config){                \r
10         this.filters = new Ext.util.MixedCollection();\r
11         this.filters.getKey = function(o) {return o ? o.dataIndex : null};\r
12         \r
13         for(var i=0, len=config.filters.length; i<len; i++) {\r
14                 this.addFilter(config.filters[i]);\r
15   }\r
16   \r
17         this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);\r
18         \r
19         delete config.filters;\r
20         Ext.apply(this, config);\r
21 };\r
22 Ext.extend(Ext.grid.GridFilters, Ext.util.Observable, {\r
23         /**\r
24          * @cfg {Integer} updateBuffer\r
25          * Number of milisecond to defer store updates since the last filter change.\r
26          */\r
27         updateBuffer: 500,\r
28         /**\r
29          * @cfg {String} paramPrefix\r
30          * The url parameter prefix for the filters.\r
31          */\r
32         paramPrefix: 'filter',\r
33         /**\r
34          * @cfg {String} fitlerCls\r
35          * The css class to be applied to column headers that active filters. Defaults to 'ux-filterd-column'\r
36          */\r
37         filterCls: 'ux-filtered-column',\r
38         /**\r
39          * @cfg {Boolean} local\r
40          * True to use Ext.data.Store filter functions instead of server side filtering.\r
41          */\r
42         local: false,\r
43         /**\r
44          * @cfg {Boolean} autoReload\r
45          * True to automagicly reload the datasource when a filter change happens.\r
46          */\r
47         autoReload: true,\r
48         /**\r
49          * @cfg {String} stateId\r
50          * Name of the Ext.data.Store value to be used to store state information.\r
51          */\r
52         stateId: undefined,\r
53         /**\r
54          * @cfg {Boolean} showMenu\r
55          * True to show the filter menus\r
56          */\r
57         showMenu: true,\r
58     /**\r
59      * @cfg {String} filtersText\r
60      * The text displayed for the "Filters" menu item\r
61      */\r
62     filtersText: 'Filters',\r
63 \r
64         init: function(grid){\r
65     if(grid instanceof Ext.grid.GridPanel){\r
66       this.grid  = grid;\r
67       \r
68       this.store = this.grid.getStore();\r
69       if(this.local){\r
70         this.store.on('load', function(store) {\r
71           store.filterBy(this.getRecordFilter());\r
72         }, this);\r
73       } else {\r
74         this.store.on('beforeload', this.onBeforeLoad, this);\r
75       }\r
76       \r
77       this.grid.filters = this;\r
78       \r
79       this.grid.addEvents('filterupdate');\r
80       \r
81       grid.on("render", this.onRender, this);\r
82       grid.on("beforestaterestore", this.applyState, this);\r
83       grid.on("beforestatesave", this.saveState, this);\r
84       \r
85     } else if(grid instanceof Ext.PagingToolbar) {\r
86       this.toolbar = grid;\r
87     }\r
88         },\r
89                 \r
90         /** private **/\r
91         applyState: function(grid, state) {\r
92                 this.suspendStateStore = true;\r
93                 this.clearFilters();\r
94                 if(state.filters) {\r
95                         for(var key in state.filters) {\r
96                                 var filter = this.filters.get(key);\r
97                                 if(filter) {\r
98                                         filter.setValue(state.filters[key]);\r
99                                         filter.setActive(true);\r
100                                 }\r
101                         }\r
102     }\r
103     \r
104                 this.deferredUpdate.cancel();\r
105                 if(this.local) {\r
106                         this.reload();\r
107     }\r
108     \r
109                 this.suspendStateStore = false;\r
110         },\r
111         \r
112         /** private **/\r
113         saveState: function(grid, state){\r
114                 var filters = {};\r
115                 this.filters.each(function(filter) {\r
116                         if(filter.active) {\r
117                                 filters[filter.dataIndex] = filter.getValue();\r
118       }\r
119                 });\r
120                 return state.filters = filters;\r
121         },\r
122         \r
123         /** private **/\r
124         onRender: function(){\r
125                 var hmenu;\r
126                 \r
127                 if(this.showMenu) {\r
128                         hmenu = this.grid.getView().hmenu;\r
129                         \r
130                         this.sep  = hmenu.addSeparator();\r
131                         this.menu = hmenu.add(new Ext.menu.CheckItem({\r
132                                         text: this.filtersText,\r
133                                         menu: new Ext.menu.Menu()\r
134                                 }));\r
135                         this.menu.on('checkchange', this.onCheckChange, this);\r
136                         this.menu.on('beforecheckchange', this.onBeforeCheck, this);\r
137                                 \r
138                         hmenu.on('beforeshow', this.onMenu, this);\r
139                 }\r
140                 \r
141                 this.grid.getView().on("refresh", this.onRefresh, this);\r
142                 this.updateColumnHeadings(this.grid.getView());\r
143         },\r
144         \r
145         /** private **/\r
146         onMenu: function(filterMenu) {\r
147                 var filter = this.getMenuFilter();\r
148                 if(filter) {\r
149                         this.menu.menu = filter.menu;\r
150                         this.menu.setChecked(filter.active, false);\r
151                 }\r
152                 \r
153                 this.menu.setVisible(filter !== undefined);\r
154                 this.sep.setVisible(filter !== undefined);\r
155         },\r
156         \r
157         /** private **/\r
158         onCheckChange: function(item, value) {\r
159                 this.getMenuFilter().setActive(value);\r
160         },\r
161         \r
162         /** private **/\r
163         onBeforeCheck: function(check, value) {\r
164                 return !value || this.getMenuFilter().isActivatable();\r
165         },\r
166         \r
167         /** private **/\r
168         onStateChange: function(event, filter) {\r
169     if(event == "serialize") {\r
170       return;\r
171     }\r
172     \r
173                 if(filter == this.getMenuFilter()) {\r
174                         this.menu.setChecked(filter.active, false);\r
175     }\r
176                         \r
177                 if(this.autoReload || this.local) {\r
178                         this.deferredUpdate.delay(this.updateBuffer);\r
179     }\r
180                 \r
181                 var view = this.grid.getView();\r
182                 this.updateColumnHeadings(view);\r
183                         \r
184                 this.grid.saveState();\r
185                         \r
186                 this.grid.fireEvent('filterupdate', this, filter);\r
187         },\r
188         \r
189         /** private **/\r
190         onBeforeLoad: function(store, options) {\r
191     options.params = options.params || {};\r
192                 this.cleanParams(options.params);               \r
193                 var params = this.buildQuery(this.getFilterData());\r
194                 Ext.apply(options.params, params);\r
195         },\r
196         \r
197         /** private **/\r
198         onRefresh: function(view) {\r
199                 this.updateColumnHeadings(view);\r
200         },\r
201         \r
202         /** private **/\r
203         getMenuFilter: function() {\r
204                 var view = this.grid.getView();\r
205                 if(!view || view.hdCtxIndex === undefined) {\r
206                         return null;\r
207     }\r
208                 \r
209                 return this.filters.get(view.cm.config[view.hdCtxIndex].dataIndex);\r
210         },\r
211         \r
212         /** private **/\r
213         updateColumnHeadings: function(view) {\r
214                 if(!view || !view.mainHd) {\r
215       return;\r
216     }\r
217                 \r
218                 var hds = view.mainHd.select('td').removeClass(this.filterCls);\r
219                 for(var i=0, len=view.cm.config.length; i<len; i++) {\r
220                         var filter = this.getFilter(view.cm.config[i].dataIndex);\r
221                         if(filter && filter.active) {\r
222                                 hds.item(i).addClass(this.filterCls);\r
223       }\r
224                 }\r
225         },\r
226         \r
227         /** private **/\r
228         reload: function() {\r
229                 if(this.local){\r
230                         this.grid.store.clearFilter(true);\r
231                         this.grid.store.filterBy(this.getRecordFilter());\r
232                 } else {\r
233                         this.deferredUpdate.cancel();\r
234                         var store = this.grid.store;\r
235                         if(this.toolbar) {\r
236                                 var start = this.toolbar.paramNames.start;\r
237                                 if(store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {\r
238                                         store.lastOptions.params[start] = 0;\r
239         }\r
240                         }\r
241                         store.reload();\r
242                 }\r
243         },\r
244         \r
245         /**\r
246          * Method factory that generates a record validator for the filters active at the time\r
247          * of invokation.\r
248          * \r
249          * @private\r
250          */\r
251         getRecordFilter: function() {\r
252                 var f = [];\r
253                 this.filters.each(function(filter) {\r
254                         if(filter.active) {\r
255         f.push(filter);\r
256       }\r
257                 });\r
258                 \r
259                 var len = f.length;\r
260                 return function(record) {\r
261                         for(var i=0; i<len; i++) {\r
262                                 if(!f[i].validateRecord(record)) {\r
263                                         return false;\r
264         }\r
265       }\r
266                         return true;\r
267                 };\r
268         },\r
269         \r
270         /**\r
271          * Adds a filter to the collection.\r
272          * \r
273          * @param {Object/Ext.grid.filter.Filter} config A filter configuration or a filter object.\r
274          * \r
275          * @return {Ext.grid.filter.Filter} The existing or newly created filter object.\r
276          */\r
277         addFilter: function(config) {\r
278                 var filter = config.menu ? config : new (this.getFilterClass(config.type))(config);\r
279                 this.filters.add(filter);\r
280                 \r
281                 Ext.util.Observable.capture(filter, this.onStateChange, this);\r
282                 return filter;\r
283         },\r
284         \r
285         /**\r
286          * Returns a filter for the given dataIndex, if on exists.\r
287          * \r
288          * @param {String} dataIndex The dataIndex of the desired filter object.\r
289          * \r
290          * @return {Ext.grid.filter.Filter}\r
291          */\r
292         getFilter: function(dataIndex){\r
293                 return this.filters.get(dataIndex);\r
294         },\r
295 \r
296         /**\r
297          * Turns all filters off. This does not clear the configuration information.\r
298          */\r
299         clearFilters: function() {\r
300                 this.filters.each(function(filter) {\r
301                         filter.setActive(false);\r
302                 });\r
303         },\r
304 \r
305         /** private **/\r
306         getFilterData: function() {\r
307                 var filters = [];\r
308                 \r
309                 this.filters.each(function(f) {\r
310                         if(f.active) {\r
311                                 var d = [].concat(f.serialize());\r
312                                 for(var i=0, len=d.length; i<len; i++) {\r
313                                         filters.push({field: f.dataIndex, data: d[i]});\r
314         }\r
315                         }\r
316                 });\r
317                 \r
318                 return filters;\r
319         },\r
320         \r
321         /**\r
322          * Function to take structured filter data and 'flatten' it into query parameteres. The default function\r
323          * will produce a query string of the form:\r
324          *              filters[0][field]=dataIndex&filters[0][data][param1]=param&filters[0][data][param2]=param...\r
325          * \r
326          * @param {Array} filters A collection of objects representing active filters and their configuration.\r
327          *        Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured\r
328          *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.\r
329          * \r
330          * @return {Object} Query keys and values\r
331          */\r
332         buildQuery: function(filters) {\r
333                 var p = {};\r
334                 for(var i=0, len=filters.length; i<len; i++) {\r
335                         var f = filters[i];\r
336                         var root = [this.paramPrefix, '[', i, ']'].join('');\r
337                         p[root + '[field]'] = f.field;\r
338                         \r
339                         var dataPrefix = root + '[data]';\r
340                         for(var key in f.data) {\r
341                                 p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];\r
342       }\r
343                 }\r
344                 \r
345                 return p;\r
346         },\r
347         \r
348         /**\r
349          * Removes filter related query parameters from the provided object.\r
350          * \r
351          * @param {Object} p Query parameters that may contain filter related fields.\r
352          */\r
353         cleanParams: function(p) {\r
354                 var regex = new RegExp("^" + this.paramPrefix + "\[[0-9]+\]");\r
355                 for(var key in p) {\r
356                         if(regex.test(key)) {\r
357                                 delete p[key];\r
358       }\r
359     }\r
360         },\r
361         \r
362         /**\r
363          * Function for locating filter classes, overwrite this with your favorite\r
364          * loader to provide dynamic filter loading.\r
365          * \r
366          * @param {String} type The type of filter to load.\r
367          * \r
368          * @return {Class}\r
369          */\r
370         getFilterClass: function(type){\r
371                 return Ext.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];\r
372         }\r
373 });