otsdaq_utilities  v2_05_02_indev
filter.js
1 var Filter = function(table){
2 
3  this.table = table; //hold Tabulator object
4 
5  this.filterList = []; //hold filter list
6  this.headerFilters = {}; //hold column filters
7  this.headerFilterColumns = []; //hold columns that use header filters
8 
9  this.changed = false; //has filtering changed since last render
10 };
11 
12 
13 //initialize column header filter
14 Filter.prototype.initializeColumn = function(column, value){
15  var self = this,
16  field = column.getField(),
17  params;
18 
19 
20  //handle successfull value change
21  function success(value){
22  var filterType = (column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text") || column.modules.filter.tagType == "textarea" ? "partial" : "match",
23  type = "",
24  filterFunc;
25 
26  if(typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value){
27 
28  column.modules.filter.prevSuccess = value;
29 
30  if(!column.modules.filter.emptyFunc(value)){
31  column.modules.filter.value = value;
32 
33  switch(typeof column.definition.headerFilterFunc){
34  case "string":
35  if(self.filters[column.definition.headerFilterFunc]){
36  type = column.definition.headerFilterFunc;
37  filterFunc = function(data){
38  var params = column.definition.headerFilterFuncParams || {};
39  var fieldVal = column.getFieldValue(data);
40 
41  params = typeof params === "function" ? params(value, fieldVal, data) : params;
42 
43  return self.filters[column.definition.headerFilterFunc](value, fieldVal, data, params);
44  };
45  }else{
46  console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc);
47  }
48  break;
49 
50  case "function":
51  filterFunc = function(data){
52  var params = column.definition.headerFilterFuncParams || {};
53  var fieldVal = column.getFieldValue(data);
54 
55  params = typeof params === "function" ? params(value, fieldVal, data) : params;
56 
57  return column.definition.headerFilterFunc(value, fieldVal, data, params);
58  };
59 
60  type = filterFunc;
61  break;
62  }
63 
64  if(!filterFunc){
65  switch(filterType){
66  case "partial":
67  filterFunc = function(data){
68  var colVal = column.getFieldValue(data);
69 
70  if(typeof colVal !== 'undefined' && colVal !== null){
71  return String(colVal).toLowerCase().indexOf(String(value).toLowerCase()) > -1;
72  }else{
73  return false;
74  }
75  };
76  type = "like";
77  break;
78 
79  default:
80  filterFunc = function(data){
81  return column.getFieldValue(data) == value;
82  };
83  type = "=";
84  }
85  }
86 
87  self.headerFilters[field] = {value:value, func:filterFunc, type:type};
88 
89  }else{
90  delete self.headerFilters[field];
91  }
92 
93  self.changed = true;
94 
95  self.table.rowManager.filterRefresh();
96  }
97 
98  return true;
99  }
100 
101  column.modules.filter = {
102  success:success,
103  attrType:false,
104  tagType:false,
105  emptyFunc:false,
106  };
107 
108  this.generateHeaderFilterElement(column);
109 };
110 
111 Filter.prototype.generateHeaderFilterElement = function(column, initialValue, reinitialize){
112  var self = this,
113  success = column.modules.filter.success,
114  field = column.getField(),
115  filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params;
116 
117  //handle aborted edit
118  function cancel(){}
119 
120  if(column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode){
121  column.contentElement.removeChild(column.modules.filter.headerElement.parentNode);
122  }
123 
124  if(field){
125 
126  //set empty value function
127  column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value){
128  return !value && value !== "0";
129  };
130 
131  filterElement = document.createElement("div");
132  filterElement.classList.add("tabulator-header-filter");
133 
134  //set column editor
135  switch(typeof column.definition.headerFilter){
136  case "string":
137  if(self.table.modules.edit.editors[column.definition.headerFilter]){
138  editor = self.table.modules.edit.editors[column.definition.headerFilter];
139 
140  if((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck){
141  column.modules.filter.emptyFunc = function(value){
142  return value !== true && value !== false;
143  };
144  }
145  }else{
146  console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor);
147  }
148  break;
149 
150  case "function":
151  editor = column.definition.headerFilter;
152  break;
153 
154  case "boolean":
155  if(column.modules.edit && column.modules.edit.editor){
156  editor = column.modules.edit.editor;
157  }else{
158  if(column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]){
159  editor = self.table.modules.edit.editors[column.definition.formatter];
160 
161  if((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck){
162  column.modules.filter.emptyFunc = function(value){
163  return value !== true && value !== false;
164  };
165  }
166  }else{
167  editor = self.table.modules.edit.editors["input"];
168  }
169  }
170  break;
171  }
172 
173  if(editor){
174 
175  cellWrapper = {
176  getValue:function(){
177  return typeof initialValue !== "undefined" ? initialValue : "";
178  },
179  getField:function(){
180  return column.definition.field;
181  },
182  getElement:function(){
183  return filterElement;
184  },
185  getColumn:function(){
186  return column.getComponent();
187  },
188  getRow:function(){
189  return {
190  normalizeHeight:function(){
191 
192  }
193  };
194  }
195  };
196 
197  params = column.definition.headerFilterParams || {};
198 
199  params = typeof params === "function" ? params.call(self.table) : params;
200 
201  editorElement = editor.call(this.table.modules.edit, cellWrapper, function(){}, success, cancel, params);
202 
203  if(!editorElement){
204  console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false");
205  return;
206  }
207 
208  if(!(editorElement instanceof Node)){
209  console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement);
210  return;
211  }
212 
213  //set Placeholder Text
214  if(field){
215  self.table.modules.localize.bind("headerFilters|columns|" + column.definition.field, function(value){
216  editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : self.table.modules.localize.getText("headerFilters|default"));
217  });
218  }else{
219  self.table.modules.localize.bind("headerFilters|default", function(value){
220  editorElement.setAttribute("placeholder", typeof self.column.definition.headerFilterPlaceholder !== "undefined" && self.column.definition.headerFilterPlaceholder ? self.column.definition.headerFilterPlaceholder : value);
221  });
222  }
223 
224  //focus on element on click
225  editorElement.addEventListener("click", function(e){
226  e.stopPropagation();
227  editorElement.focus();
228  });
229 
230  editorElement.addEventListener("focus", (e) => {
231  var left = this.table.columnManager.element.scrollLeft;
232 
233  if(left !== this.table.rowManager.element.scrollLeft){
234  this.table.rowManager.scrollHorizontal(left);
235  this.table.columnManager.scrollHorizontal(left);
236  }
237  })
238 
239  //live update filters as user types
240  typingTimer = false;
241 
242  searchTrigger = function(e){
243  if(typingTimer){
244  clearTimeout(typingTimer);
245  }
246 
247  typingTimer = setTimeout(function(){
248  success(editorElement.value);
249  },self.table.options.headerFilterLiveFilterDelay);
250  };
251 
252  column.modules.filter.headerElement = editorElement;
253  column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : "" ;
254  column.modules.filter.tagType = editorElement.tagName.toLowerCase();
255 
256  if(column.definition.headerFilterLiveFilter !== false){
257 
258  if (
259  !(
260  column.definition.headerFilter === 'autocomplete' ||
261  column.definition.headerFilter === 'tickCross' ||
262  ((column.definition.editor === 'autocomplete' ||
263  column.definition.editor === 'tickCross') &&
264  column.definition.headerFilter === true)
265  )
266  ) {
267  editorElement.addEventListener("keyup", searchTrigger);
268  editorElement.addEventListener("search", searchTrigger);
269 
270 
271  //update number filtered columns on change
272  if(column.modules.filter.attrType == "number"){
273  editorElement.addEventListener("change", function(e){
274  success(editorElement.value);
275  });
276  }
277 
278  //change text inputs to search inputs to allow for clearing of field
279  if(column.modules.filter.attrType == "text" && this.table.browser !== "ie"){
280  editorElement.setAttribute("type", "search");
281  // editorElement.off("change blur"); //prevent blur from triggering filter and preventing selection click
282  }
283 
284  }
285 
286  //prevent input and select elements from propegating click to column sorters etc
287  if(column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea"){
288  editorElement.addEventListener("mousedown",function(e){
289  e.stopPropagation();
290  });
291  }
292  }
293 
294  filterElement.appendChild(editorElement);
295 
296  column.contentElement.appendChild(filterElement);
297 
298  if(!reinitialize){
299  self.headerFilterColumns.push(column);
300  }
301  }
302  }else{
303  console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title);
304  }
305 };
306 
307 //hide all header filter elements (used to ensure correct column widths in "fitData" layout mode)
308 Filter.prototype.hideHeaderFilterElements = function(){
309  this.headerFilterColumns.forEach(function(column){
310  if(column.modules.filter && column.modules.filter.headerElement){
311  column.modules.filter.headerElement.style.display = 'none';
312  }
313  });
314 };
315 
316 //show all header filter elements (used to ensure correct column widths in "fitData" layout mode)
317 Filter.prototype.showHeaderFilterElements = function(){
318  this.headerFilterColumns.forEach(function(column){
319  if(column.modules.filter && column.modules.filter.headerElement){
320  column.modules.filter.headerElement.style.display = '';
321  }
322  });
323 };
324 
325 
326 //programatically set value of header filter
327 Filter.prototype.setHeaderFilterFocus = function(column){
328  if(column.modules.filter && column.modules.filter.headerElement){
329  column.modules.filter.headerElement.focus();
330  }else{
331  console.warn("Column Filter Focus Error - No header filter set on column:", column.getField());
332  }
333 };
334 
335 //programatically set value of header filter
336 Filter.prototype.setHeaderFilterValue = function(column, value){
337  if (column){
338  if(column.modules.filter && column.modules.filter.headerElement){
339  this.generateHeaderFilterElement(column, value, true);
340  column.modules.filter.success(value);
341  }else{
342  console.warn("Column Filter Error - No header filter set on column:", column.getField());
343  }
344  }
345 };
346 
347 Filter.prototype.reloadHeaderFilter = function(column){
348  if (column){
349  if(column.modules.filter && column.modules.filter.headerElement){
350  this.generateHeaderFilterElement(column, column.modules.filter.value, true);
351  }else{
352  console.warn("Column Filter Error - No header filter set on column:", column.getField());
353  }
354  }
355 };
356 
357 //check if the filters has changed since last use
358 Filter.prototype.hasChanged = function(){
359  var changed = this.changed;
360  this.changed = false;
361  return changed;
362 };
363 
364 //set standard filters
365 Filter.prototype.setFilter = function(field, type, value){
366  var self = this;
367 
368  self.filterList = [];
369 
370  if(!Array.isArray(field)){
371  field = [{field:field, type:type, value:value}];
372  }
373 
374  self.addFilter(field);
375 
376 };
377 
378 //add filter to array
379 Filter.prototype.addFilter = function(field, type, value){
380  var self = this;
381 
382  if(!Array.isArray(field)){
383  field = [{field:field, type:type, value:value}];
384  }
385 
386  field.forEach(function(filter){
387 
388  filter = self.findFilter(filter);
389 
390  if(filter){
391  self.filterList.push(filter);
392 
393  self.changed = true;
394  }
395  });
396 
397  if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.filter){
398  this.table.modules.persistence.save("filter");
399  }
400 
401 };
402 
403 Filter.prototype.findFilter = function(filter){
404  var self = this,
405  column;
406 
407  if(Array.isArray(filter)){
408  return this.findSubFilters(filter);
409  }
410 
411 
412  var filterFunc = false;
413 
414  if(typeof filter.field == "function"){
415  filterFunc = function(data){
416  return filter.field(data, filter.type || {})// pass params to custom filter function
417  }
418  }else{
419 
420  if(self.filters[filter.type]){
421 
422  column = self.table.columnManager.getColumnByField(filter.field);
423 
424  if(column){
425  filterFunc = function(data){
426  return self.filters[filter.type](filter.value, column.getFieldValue(data));
427  }
428  }else{
429  filterFunc = function(data){
430  return self.filters[filter.type](filter.value, data[filter.field]);
431  }
432  }
433 
434 
435  }else{
436  console.warn("Filter Error - No such filter type found, ignoring: ", filter.type);
437  }
438  }
439 
440 
441  filter.func = filterFunc;
442 
443 
444 
445  return filter.func ? filter : false;
446 };
447 
448 Filter.prototype.findSubFilters = function(filters){
449  var self = this,
450  output = [];
451 
452  filters.forEach(function(filter){
453  filter = self.findFilter(filter);
454 
455  if(filter){
456  output.push(filter);
457  }
458  });
459 
460  return output.length ? output : false;
461 }
462 
463 
464 //get all filters
465 Filter.prototype.getFilters = function(all, ajax){
466  var output = [];
467 
468  if(all){
469  output = this.getHeaderFilters();
470  }
471 
472  if(ajax){
473  output.forEach(function(item){
474  if(typeof item.type == "function"){
475  item.type = "function";
476  }
477  })
478  }
479 
480  output = output.concat(this.filtersToArray(this.filterList, ajax));
481 
482  return output;
483 };
484 
485 //filter to Object
486 Filter.prototype.filtersToArray = function(filterList, ajax){
487  var output = [];
488 
489  filterList.forEach((filter) => {
490  var item;
491 
492  if(Array.isArray(filter)){
493  output.push(this.filtersToArray(filter, ajax));
494  }else{
495  item = {field:filter.field, type:filter.type, value:filter.value}
496 
497  if(ajax){
498  if(typeof item.type == "function"){
499  item.type = "function";
500  }
501  }
502 
503  output.push(item);
504  }
505  });
506 
507  return output;
508 }
509 
510 //get all filters
511 Filter.prototype.getHeaderFilters = function(){
512  var self = this,
513  output = [];
514 
515  for(var key in this.headerFilters){
516  output.push({field:key, type:this.headerFilters[key].type, value:this.headerFilters[key].value});
517  }
518 
519  return output;
520 };
521 
522 //remove filter from array
523 Filter.prototype.removeFilter = function(field, type, value){
524  var self = this;
525 
526  if(!Array.isArray(field)){
527  field = [{field:field, type:type, value:value}];
528  }
529 
530  field.forEach(function(filter){
531  var index = -1;
532 
533  if(typeof filter.field == "object"){
534  index = self.filterList.findIndex(function(element){
535  return filter === element;
536  });
537  }else{
538  index = self.filterList.findIndex(function(element){
539  return filter.field === element.field && filter.type === element.type && filter.value === element.value
540  });
541  }
542 
543  if(index > -1){
544  self.filterList.splice(index, 1);
545  self.changed = true;
546  }else{
547  console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type);
548  }
549 
550  });
551 
552  if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.filter){
553  this.table.modules.persistence.save("filter");
554  }
555 
556 };
557 
558 //clear filters
559 Filter.prototype.clearFilter = function(all){
560  this.filterList = [];
561 
562  if(all){
563  this.clearHeaderFilter();
564  }
565 
566  this.changed = true;
567 
568  if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.filter){
569  this.table.modules.persistence.save("filter");
570  }
571 };
572 
573 //clear header filters
574 Filter.prototype.clearHeaderFilter = function(){
575  var self = this;
576 
577  this.headerFilters = {};
578 
579  this.headerFilterColumns.forEach(function(column){
580  column.modules.filter.value = null;
581  column.modules.filter.prevSuccess = undefined;
582  self.reloadHeaderFilter(column);
583  });
584 
585  this.changed = true;
586 };
587 
588 //search data and return matching rows
589 Filter.prototype.search = function (searchType, field, type, value){
590  var self = this,
591  activeRows = [],
592  filterList = [];
593 
594  if(!Array.isArray(field)){
595  field = [{field:field, type:type, value:value}];
596  }
597 
598  field.forEach(function(filter){
599  filter = self.findFilter(filter);
600 
601  if(filter){
602  filterList.push(filter);
603  }
604  });
605 
606  this.table.rowManager.rows.forEach(function(row){
607  var match = true;
608 
609  filterList.forEach(function(filter){
610  if(!self.filterRecurse(filter, row.getData())){
611  match = false;
612  }
613  });
614 
615  if(match){
616  activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent());
617  }
618 
619  });
620 
621  return activeRows;
622 };
623 
624 //filter row array
625 Filter.prototype.filter = function(rowList, filters){
626  var self = this,
627  activeRows = [],
628  activeRowComponents = [];
629 
630  if(self.table.options.dataFiltering){
631  self.table.options.dataFiltering.call(self.table, self.getFilters());
632  }
633 
634  if(!self.table.options.ajaxFiltering && (self.filterList.length || Object.keys(self.headerFilters).length)){
635 
636  rowList.forEach(function(row){
637  if(self.filterRow(row)){
638  activeRows.push(row);
639  }
640  });
641 
642  }else{
643  activeRows = rowList.slice(0);
644  }
645 
646  if(self.table.options.dataFiltered){
647 
648  activeRows.forEach(function(row){
649  activeRowComponents.push(row.getComponent());
650  });
651 
652  self.table.options.dataFiltered.call(self.table, self.getFilters(), activeRowComponents);
653  }
654 
655  return activeRows;
656 
657 };
658 
659 //filter individual row
660 Filter.prototype.filterRow = function(row, filters){
661  var self = this,
662  match = true,
663  data = row.getData();
664 
665  self.filterList.forEach(function(filter){
666  if(!self.filterRecurse(filter, data)){
667  match = false;
668  }
669  });
670 
671 
672  for(var field in self.headerFilters){
673  if(!self.headerFilters[field].func(data)){
674  match = false;
675  }
676  }
677 
678  return match;
679 };
680 
681 Filter.prototype.filterRecurse = function(filter, data){
682  var self = this,
683  match = false;
684 
685  if(Array.isArray(filter)){
686  filter.forEach(function(subFilter){
687  if(self.filterRecurse(subFilter, data)){
688  match = true;
689  }
690  });
691  }else{
692  match = filter.func(data);
693  }
694 
695  return match;
696 };
697 
698 
699 
700 //list of available filters
701 Filter.prototype.filters ={
702 
703  //equal to
704  "=":function(filterVal, rowVal, rowData, filterParams){
705  return rowVal == filterVal ? true : false;
706  },
707 
708  //less than
709  "<":function(filterVal, rowVal, rowData, filterParams){
710  return rowVal < filterVal ? true : false;
711  },
712 
713  //less than or equal to
714  "<=":function(filterVal, rowVal, rowData, filterParams){
715  return rowVal <= filterVal ? true : false;
716  },
717 
718  //greater than
719  ">":function(filterVal, rowVal, rowData, filterParams){
720  return rowVal > filterVal ? true : false;
721  },
722 
723  //greater than or equal to
724  ">=":function(filterVal, rowVal, rowData, filterParams){
725  return rowVal >= filterVal ? true : false;
726  },
727 
728  //not equal to
729  "!=":function(filterVal, rowVal, rowData, filterParams){
730  return rowVal != filterVal ? true : false;
731  },
732 
733  "regex":function(filterVal, rowVal, rowData, filterParams){
734 
735  if(typeof filterVal == "string"){
736  filterVal = new RegExp(filterVal);
737  }
738 
739  return filterVal.test(rowVal);
740  },
741 
742  //contains the string
743  "like":function(filterVal, rowVal, rowData, filterParams){
744  if(filterVal === null || typeof filterVal === "undefined"){
745  return rowVal === filterVal ? true : false;
746  }else{
747  if(typeof rowVal !== 'undefined' && rowVal !== null){
748  return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1;
749  }
750  else{
751  return false;
752  }
753  }
754  },
755 
756  //in array
757  "in":function(filterVal, rowVal, rowData, filterParams){
758  if(Array.isArray(filterVal)){
759  return filterVal.indexOf(rowVal) > -1;
760  }else{
761  console.warn("Filter Error - filter value is not an array:", filterVal);
762  return false;
763  }
764  },
765 };
766 
767 Tabulator.prototype.registerModule("filter", Filter);