1 /** 2 * @author Gillis Haasnoot <gillis.haasnoot@gmail.com> 3 * @package Banana.Controls 4 * @summary Datagrid Filter Manager 5 */ 6 7 goog.provide('Banana.Controls.DataGridFilterManager'); 8 9 /** @namespace Banana.Controls.DataGridFilterManager */ 10 namespace('Banana.Controls').DataGridFilterManager = Banana.Control.extend( 11 /** @lends Banana.Controls.DataGridFilterManager.prototype */ 12 { 13 14 /** 15 * Creates a Filter Manager for usage in a datagrid. It centralize filter functionality of 16 * a datagrid by keeping track of filter changes and history. It saves data from the filters 17 * in the url and cookie. 18 * 19 * Filters should be added by calling setFilters([]); 20 * 21 * Filters should have an interface that at least contains: 22 * 23 * function getAllKey(): returns the key that should be used when "all" is selected 24 * 25 * Filters can trigger the following events: 26 * 27 * - filterUrlChanged: triggered when url param of the filter is changed 28 * - filterDataChanged: triggered when the filter data is changed 29 * 30 * Example 31 32 33 var filterManager= new Banana.Controls.DataGridFilterManager(); 34 filterManager.setUrlPreFix(pageId); 35 36 //everytime a filter changes, we come here. this could be the place to reload/update 37 //the data of the datagrid 38 filterManager.bind('filtersChanged',this.getProxy(function(e,filterData) 39 { 40 this.populateData(filterData); 41 })); 42 43 //assign some filters to the filterManager 44 filterManager.setFilters([ 45 46 new Banana.Controls.DataGridDropDownFilter() 47 .setFilterField('type') 48 .setAllTitle('All Program types') 49 .dataSetSourceBind('programtypes','programtypes'), 50 51 new Banana.Controls.DataGridDropDownFilter() 52 .setFilterField('country') 53 .setAllTitle('All Series') 54 .dataSetSourceBind('programSeries','items'), 55 56 new Banana.Controls.DataGridCheckboxFilter() 57 .setFilterField('archive') 58 .setTitle('Display archived items'), 59 60 new Banana.Controls.DataGridSearchFilter() 61 .setFilterField('search'), 62 63 this.pagerFilter = new Banana.Controls.DataGridPagerFilter() 64 .setFilterField('pageIndex') 65 .dataSetSourceBind('dataset','pageCount') 66 .dataSetBind('dataset','pageIndex') 67 ]); 68 69 70 //this is an array of filter controls. Just add them somewhere on your site. 71 //You can also add them in a DatagridControlPanel. It will automatically align them nicely. 72 var filters = filterManager.getFilters(); 73 * 74 * 75 * @constructs 76 * @extends Banana.Control 77 */ 78 init : function() 79 { 80 this._super(); 81 this.enableCookie = true; 82 this.showBindedFiltersOnly = false; 83 this.forcedInvisibleFilters = []; 84 this.filters = []; 85 this.filterKeys = []; //we save names. during unload we dont have the filters anymore. already removed 86 this.urlPreFix = ''; 87 }, 88 89 /** 90 * when called we do not auto adjust filter visibility. 91 * Normaly filters are hidden when no datasource is inside the filter. 92 * @return {this} 93 */ 94 ignoreAutoVisible : function() 95 { 96 this.ignoreAutoVisible = true; 97 return this; 98 }, 99 100 101 /** 102 * when called we only show filters which are binded with data 103 * @return this; 104 */ 105 showBindedOnly : function() 106 { 107 this.showBindedFiltersOnly = true; 108 return this; 109 }, 110 111 /** 112 * Filters can contain a so called all value. This value is used in cases where 113 * the user wants to unselect the filter. Normally we don't show this value in the url. 114 * This method will make that value visible. Useful when you need to know if a user 115 * selected the all value after coming from another page. 116 * 117 * @param {Boolean} bool 118 * @return {this} 119 */ 120 setKeepAllValueInUrl : function(bool) 121 { 122 this.keepAllValueVisible = bool; 123 return this; 124 }, 125 126 /** 127 * a prefix to save before every url param (cookie included) 128 * we do this if we have 2 datagrids and dont want to let duplicated 129 * filter params interfer with each other. 130 * 131 * @param {String} pf 132 * @return {this} 133 */ 134 setUrlPreFix : function(pf) 135 { 136 this.urlPreFix = pf; 137 return this; 138 }, 139 140 /** 141 * When invoked we will not try to restore filter states based on history 142 * @return {this} 143 */ 144 disableHistory : function() 145 { 146 this.enableCookie = false; 147 return this; 148 }, 149 150 /** 151 * Set filters for this filtermanager. 152 * Each filter should extend from Banana.Controls.BaseDataGridFilter 153 * The data inside the filter can be set manually on each filter. If it is not 154 * manualy set we try to fetch it from the history. That means that if a user already 155 * used the filter or url param name we set that value back into the filter. Handy when user 156 * comes back to a page he already visited. The fiters will be restored to the old state. 157 * This restore operation can be avoided by called 158 * 159 * @param {Array} filters the filters 160 * @return {this} 161 */ 162 setFilters : function(filters) 163 { 164 var enableCookie = this.enableCookie; 165 var urlPreFix = this.urlPreFix 166 167 /** 168 * Restore filter value from cookie and set in URL 169 */ 170 function restoreData(filter) 171 { 172 if (filter.data) return; //already set, we dont overwrite it 173 174 var newValue; 175 176 //only restore when cookie is enabled and page is loaded 177 //with historyparams 178 if (enableCookie && Banana.Application.loadingWithHistory) 179 { 180 newValue = Banana.Util.StateManager.getState(urlPreFix+filter.filterField); 181 182 Banana.Util.UrlManager.registerModule(urlPreFix+filter.filterField); 183 Banana.Util.UrlManager.setModule(urlPreFix+filter.filterField,newValue,true); 184 } 185 //otherwise we try to fetch the value from the url 186 else 187 { 188 newValue = Banana.Util.UrlManager.getModule(urlPreFix+filter.filterField); 189 } 190 191 filter.setData( 192 newValue 193 || f.getAllKey() || f.promptText) 194 }; 195 196 for (var i = 0, len = filters.length; i < len; i++) 197 { 198 var f = filters[i]; 199 200 restoreData(f); 201 202 f.bind('filterDataChanged',this.getProxy(this.filterDataChanged)); 203 f.bind('filterDataSourceChanged',this.getProxy(this.filterDataSourceChanged)); 204 f.bind('onSetVisible',this.getProxy(this.filterVisibilityChanged)); 205 206 //register a url listener to detect changes 207 Banana.Util.UrlManager.listenModule(urlPreFix+f.filterField,this.getProxy(this.filterUrlChanged),f); 208 209 this.filterKeys.push(urlPreFix+f.filterField); 210 211 if (!this.ignoreAutoVisible && (f instanceof Banana.Controls.ListControl && this.showBindedFiltersOnly && !f.datasource)) 212 { 213 f.setVisible(false); 214 } 215 216 this.filters.push(filters[i]); 217 } 218 219 //here we update the url for all filter params. 220 //we could also do it for each filter, but then we end up with multiple history points 221 Banana.Util.UrlManager.updateUrl(); 222 223 return this; 224 }, 225 226 /** 227 * @return {Array} of filters 228 */ 229 getFilters : function() 230 { 231 return this.filters; 232 }, 233 234 /** 235 * @param {String} filter id of the filter 236 * @return {Filter} 237 */ 238 getFilterById : function(filter) 239 { 240 if (typeof(filter) == 'string') 241 { 242 for (var i = 0, len = this.filters.length; i < len; i++) 243 { 244 if (this.filters[i].id == filter) 245 { 246 return this.filters[i]; 247 } 248 } 249 } 250 251 return null; 252 }, 253 254 /** 255 * Invoked when a filter is changed. 256 * We trigger a filtersChanged with all filter data 257 * @param {event} e 258 * @param {mixed} data of the filter 259 * @ignore 260 */ 261 filterUrlChanged : function(e,d) 262 { 263 //to prevent endless loop 264 if (this.ignoreUrlChanges) return; 265 266 var f = e.data; 267 268 f.setData(d); 269 270 //if filter data is the same as the all key (ie %) we remove the url param 271 if (!this.keepAllValueVisible && ( 272 f.getData() === null || 273 f.getData() == undefined || 274 f.getData() == f.allKey)) 275 { 276 this.removeFilterValues(f); 277 } 278 279 if (this.enableCookie) 280 { 281 Banana.Util.StateManager.setState(this.urlPreFix+f.filterField,f.getData()); 282 } 283 284 this.triggerEvent('filtersChanged',{'data':this.getFilterData(e),'filter':f}); 285 }, 286 287 /** 288 * Invoked when datasource is changed 289 * @param {event} e 290 * @ignore 291 */ 292 filterDataSourceChanged : function(e) 293 { 294 if (this.ignoreAutoVisible) return; 295 296 if (e.currentTarget.datasource) 297 { 298 e.currentTarget.setVisible(true); 299 } 300 }, 301 302 /** 303 * Invoked when data is changed 304 * @param {event} e 305 * @ignore 306 */ 307 filterDataChanged : function(e,dontTriggerChangeEvent) 308 { 309 //empty cache. 310 this.clearCache(); 311 312 //if filter data is the same as the all key (ie %) we remove the url param 313 if (!this.keepAllValueVisible && ( 314 e.currentTarget.getData() === null || 315 e.currentTarget.getData() == undefined || 316 e.currentTarget.getData() == e.currentTarget.allKey)) 317 { 318 this.removeFilterValues(e.currentTarget); 319 } 320 else 321 { 322 this.registerFilterValues(e.currentTarget); 323 } 324 325 if (dontTriggerChangeEvent) 326 { 327 return; 328 } 329 this.triggerEvent('filtersChanged',{'data':this.getFilterData(e),'filter':e.currentTarget}); 330 }, 331 332 /** 333 * triggered when visibility of a filter is changed 334 * 335 * @param {event} e 336 * @param {boolean} visible true when filter becomes visible 337 * @ignore 338 */ 339 filterVisibilityChanged : function(e,visible) 340 { 341 //if rendered and visibility is changed to false we remove url params 342 if (e.currentTarget.isRendered && !visible) 343 { 344 this.removeFilterValues(e.currentTarget); 345 } 346 347 }, 348 349 /** 350 * removes filter values from url and cookie 351 * 352 * @param filter 353 */ 354 removeFilterValues : function(filter) 355 { 356 if (typeof(filter) == 'string') 357 { 358 filter = this.getFilterById(filter); 359 } 360 361 Banana.Util.UrlManager.removeModule(this.urlPreFix+filter.filterField); 362 363 if (this.enableCookie) 364 { 365 Banana.Util.StateManager.removeState(this.urlPreFix+filter.filterField); 366 } 367 }, 368 369 /** 370 * @param {mixed} filter Filter itself or an id 371 * @param {String} value 372 */ 373 setFilterValue : function(filter,value) 374 { 375 if (typeof(filter) == 'string') 376 { 377 filter = this.getFilterById(filter); 378 } 379 380 //if filter data is the same as the all key (ie %) we remove the url param 381 if (value == filter.allKey) 382 { 383 this.removeFilterValues(filter); 384 } 385 else 386 { 387 filter.setData(value); 388 } 389 }, 390 391 /** 392 * Registers the values of the filter in the url and state 393 * @param {Filter} filter 394 * @ignore 395 */ 396 registerFilterValues : function(filter) 397 { 398 this.ignoreUrlChanges = true; 399 400 Banana.Util.UrlManager.registerModule(this.urlPreFix+filter.filterField); 401 Banana.Util.UrlManager.setModule(this.urlPreFix+filter.filterField, filter.getData()); 402 403 if (this.enableCookie) 404 { 405 Banana.Util.StateManager.setState(this.urlPreFix+filter.filterField,filter.getData()); 406 } 407 408 this.ignoreUrlChanges = false; 409 }, 410 411 /** 412 * Clears the cache of the filter 413 */ 414 clearCache : function() 415 { 416 this.filtersData = undefined; 417 }, 418 419 /** 420 * Gets the current filter data 421 * each data part contains an object with 422 * - filterField name of the filter field 423 * - data the actual data 424 * - dataChanged boolean true when the filter is changed by the user 425 * 426 * @param {boolean} ignoreCache when true we don't use the cache 427 * @param {boolean} flat when true we return key value array 428 * @param {boolean} ignoreNotVisible when true we don't include the invisble filters in the returned data 429 * 430 * @return {object} data of the filters 431 */ 432 getFilterData : function(ignoreCache,flat,ignoreNotVisible) 433 { 434 if (!this.filters) return null; 435 436 //get the cached filter data. 437 if (!ignoreCache && this.filtersData) 438 { 439 return this.filtersData; 440 } 441 442 var filterObj = {}; 443 444 for (var i =0, len = this.filters.length; i < len; i++) 445 { 446 var f = this.filters[i]; 447 448 if (f.getData() && (!f.isRendered || f.visible)) 449 { 450 filterObj[f.name || f.filterField] = ({'filterField':f.filterField,'data':f.getData(),'dataChanged':f.isChanged}); 451 } 452 } 453 454 this.filtersData = filterObj; 455 456 if (flat) 457 { 458 var flatData = {}; 459 460 for (var i =0, len = this.filters.length; i < len; i++) 461 { 462 var f = this.filters[i]; 463 464 if (f.getData() && (!f.isRendered || f.visible)) 465 { 466 flatData[f.filterField] = f.getData(); 467 } 468 } 469 470 return flatData; 471 } 472 473 return this.filtersData; 474 } 475 }); 476 477 478