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