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