UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

890 lines (699 loc) 25.1 kB
import Module from '../../core/Module.js'; import defaultFilters from './defaults/filters.js'; class Filter extends Module{ constructor(table){ super(table); this.filterList = []; //hold filter list this.headerFilters = {}; //hold column filters this.headerFilterColumns = []; //hold columns that use header filters this.prevHeaderFilterChangeCheck = ""; this.prevHeaderFilterChangeCheck = "{}"; this.changed = false; //has filtering changed since last render this.tableInitialized = false; this.registerTableOption("filterMode", "local"); //local or remote filtering this.registerTableOption("initialFilter", false); //initial filtering criteria this.registerTableOption("initialHeaderFilter", false); //initial header filtering criteria this.registerTableOption("headerFilterLiveFilterDelay", 300); //delay before updating column after user types in header filter this.registerColumnOption("headerFilter"); this.registerColumnOption("headerFilterPlaceholder"); this.registerColumnOption("headerFilterParams"); this.registerColumnOption("headerFilterEmptyCheck"); this.registerColumnOption("headerFilterFunc"); this.registerColumnOption("headerFilterFuncParams"); this.registerColumnOption("headerFilterLiveFilter"); this.registerTableFunction("searchRows", this.searchRows.bind(this)); this.registerTableFunction("searchData", this.searchData.bind(this)); this.registerTableFunction("setFilter", this.userSetFilter.bind(this)); this.registerTableFunction("refreshFilter", this.userRefreshFilter.bind(this)); this.registerTableFunction("addFilter", this.userAddFilter.bind(this)); this.registerTableFunction("getFilters", this.getFilters.bind(this)); this.registerTableFunction("setHeaderFilterFocus", this.userSetHeaderFilterFocus.bind(this)); this.registerTableFunction("getHeaderFilterValue", this.userGetHeaderFilterValue.bind(this)); this.registerTableFunction("setHeaderFilterValue", this.userSetHeaderFilterValue.bind(this)); this.registerTableFunction("getHeaderFilters", this.getHeaderFilters.bind(this)); this.registerTableFunction("removeFilter", this.userRemoveFilter.bind(this)); this.registerTableFunction("clearFilter", this.userClearFilter.bind(this)); this.registerTableFunction("clearHeaderFilter", this.userClearHeaderFilter.bind(this)); this.registerComponentFunction("column", "headerFilterFocus", this.setHeaderFilterFocus.bind(this)); this.registerComponentFunction("column", "reloadHeaderFilter", this.reloadHeaderFilter.bind(this)); this.registerComponentFunction("column", "getHeaderFilterValue", this.getHeaderFilterValue.bind(this)); this.registerComponentFunction("column", "setHeaderFilterValue", this.setHeaderFilterValue.bind(this)); } initialize(){ this.subscribe("column-init", this.initializeColumnHeaderFilter.bind(this)); this.subscribe("column-width-fit-before", this.hideHeaderFilterElements.bind(this)); this.subscribe("column-width-fit-after", this.showHeaderFilterElements.bind(this)); this.subscribe("table-built", this.tableBuilt.bind(this)); if(this.table.options.filterMode === "remote"){ this.subscribe("data-params", this.remoteFilterParams.bind(this)); } this.registerDataHandler(this.filter.bind(this), 10); } tableBuilt(){ if(this.table.options.initialFilter){ this.setFilter(this.table.options.initialFilter); } if(this.table.options.initialHeaderFilter){ this.table.options.initialHeaderFilter.forEach((item) => { var column = this.table.columnManager.findColumn(item.field); if(column){ this.setHeaderFilterValue(column, item.value); }else{ console.warn("Column Filter Error - No matching column found:", item.field); return false; } }); } this.tableInitialized = true; } remoteFilterParams(data, config, silent, params){ params.filter = this.getFilters(true, true); return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// //set standard filters userSetFilter(field, type, value, params){ this.setFilter(field, type, value, params); this.refreshFilter(); } //set standard filters userRefreshFilter(){ this.refreshFilter(); } //add filter to array userAddFilter(field, type, value, params){ this.addFilter(field, type, value, params); this.refreshFilter(); } userSetHeaderFilterFocus(field){ var column = this.table.columnManager.findColumn(field); if(column){ this.setHeaderFilterFocus(column); }else{ console.warn("Column Filter Focus Error - No matching column found:", field); return false; } } userGetHeaderFilterValue(field) { var column = this.table.columnManager.findColumn(field); if(column){ return this.getHeaderFilterValue(column); }else{ console.warn("Column Filter Error - No matching column found:", field); } } userSetHeaderFilterValue(field, value){ var column = this.table.columnManager.findColumn(field); if(column){ this.setHeaderFilterValue(column, value); }else{ console.warn("Column Filter Error - No matching column found:", field); return false; } } //remove filter from array userRemoveFilter(field, type, value){ this.removeFilter(field, type, value); this.refreshFilter(); } //clear filters userClearFilter(all){ this.clearFilter(all); this.refreshFilter(); } //clear header filters userClearHeaderFilter(){ this.clearHeaderFilter(); this.refreshFilter(); } //search for specific row components searchRows(field, type, value){ return this.search("rows", field, type, value); } //search for specific data searchData(field, type, value){ return this.search("data", field, type, value); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnHeaderFilter(column){ var def = column.definition; if(def.headerFilter){ if(typeof def.headerFilterPlaceholder !== "undefined" && def.field){ this.module("localize").setHeaderFilterColumnPlaceholder(def.field, def.headerFilterPlaceholder); } this.initializeColumn(column); } } //initialize column header filter initializeColumn(column, value){ var self = this, field = column.getField(); //handle successfully value change function success(value){ var filterType = (column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text") || column.modules.filter.tagType == "textarea" ? "partial" : "match", type = "", filterChangeCheck = "", filterFunc; if(typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value){ column.modules.filter.prevSuccess = value; if(!column.modules.filter.emptyFunc(value)){ column.modules.filter.value = value; switch(typeof column.definition.headerFilterFunc){ case "string": if(Filter.filters[column.definition.headerFilterFunc]){ type = column.definition.headerFilterFunc; filterFunc = function(data){ var params = column.definition.headerFilterFuncParams || {}; var fieldVal = column.getFieldValue(data); params = typeof params === "function" ? params(value, fieldVal, data) : params; return Filter.filters[column.definition.headerFilterFunc](value, fieldVal, data, params); }; }else{ console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc); } break; case "function": filterFunc = function(data){ var params = column.definition.headerFilterFuncParams || {}; var fieldVal = column.getFieldValue(data); params = typeof params === "function" ? params(value, fieldVal, data) : params; return column.definition.headerFilterFunc(value, fieldVal, data, params); }; type = filterFunc; break; } if(!filterFunc){ switch(filterType){ case "partial": filterFunc = function(data){ var colVal = column.getFieldValue(data); if(typeof colVal !== 'undefined' && colVal !== null){ return String(colVal).toLowerCase().indexOf(String(value).toLowerCase()) > -1; }else{ return false; } }; type = "like"; break; default: filterFunc = function(data){ return column.getFieldValue(data) == value; }; type = "="; } } self.headerFilters[field] = {value:value, func:filterFunc, type:type}; }else{ delete self.headerFilters[field]; } column.modules.filter.value = value; filterChangeCheck = JSON.stringify(self.headerFilters); if(self.prevHeaderFilterChangeCheck !== filterChangeCheck){ self.prevHeaderFilterChangeCheck = filterChangeCheck; self.trackChanges(); self.refreshFilter(); } } return true; } column.modules.filter = { success:success, attrType:false, tagType:false, emptyFunc:false, }; this.generateHeaderFilterElement(column); } generateHeaderFilterElement(column, initialValue, reinitialize){ var self = this, success = column.modules.filter.success, field = column.getField(), filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params; column.modules.filter.value = initialValue; //handle aborted edit function cancel(){} if(column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode){ column.contentElement.removeChild(column.modules.filter.headerElement.parentNode); } if(field){ //set empty value function column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value){ return !value && value !== 0; }; filterElement = document.createElement("div"); filterElement.classList.add("tabulator-header-filter"); //set column editor switch(typeof column.definition.headerFilter){ case "string": if(self.table.modules.edit.editors[column.definition.headerFilter]){ editor = self.table.modules.edit.editors[column.definition.headerFilter]; if((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck){ column.modules.filter.emptyFunc = function(value){ return value !== true && value !== false; }; } }else{ console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor); } break; case "function": editor = column.definition.headerFilter; break; case "boolean": if(column.modules.edit && column.modules.edit.editor){ editor = column.modules.edit.editor; }else{ if(column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]){ editor = self.table.modules.edit.editors[column.definition.formatter]; if((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck){ column.modules.filter.emptyFunc = function(value){ return value !== true && value !== false; }; } }else{ editor = self.table.modules.edit.editors["input"]; } } break; } if(editor){ cellWrapper = { getValue:function(){ return typeof initialValue !== "undefined" ? initialValue : ""; }, getField:function(){ return column.definition.field; }, getElement:function(){ return filterElement; }, getColumn:function(){ return column.getComponent(); }, getTable:() => { return this.table; }, getRow:function(){ return { normalizeHeight:function(){ } }; } }; params = column.definition.headerFilterParams || {}; params = typeof params === "function" ? params.call(self.table, cellWrapper) : params; editorElement = editor.call(this.table.modules.edit, cellWrapper, function(){}, success, cancel, params); if(!editorElement){ console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false"); return; } if(!(editorElement instanceof Node)){ console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement); return; } //set Placeholder Text self.langBind("headerFilters|columns|" + column.definition.field, function(value){ editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : self.langText("headerFilters|default")); }); //focus on element on click editorElement.addEventListener("click", function(e){ e.stopPropagation(); editorElement.focus(); }); editorElement.addEventListener("focus", (e) => { var left = this.table.columnManager.element.scrollLeft; var headerPos = this.table.rowManager.element.scrollLeft + parseInt(this.table.columnManager.element.style.marginLeft); if(left !== headerPos){ this.table.rowManager.scrollHorizontal(left); this.table.columnManager.scrollHorizontal(left); } }); //live update filters as user types typingTimer = false; searchTrigger = function(e){ if(typingTimer){ clearTimeout(typingTimer); } typingTimer = setTimeout(function(){ success(editorElement.value); },self.table.options.headerFilterLiveFilterDelay); }; column.modules.filter.headerElement = editorElement; column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : "" ; column.modules.filter.tagType = editorElement.tagName.toLowerCase(); if(column.definition.headerFilterLiveFilter !== false){ if ( !( column.definition.headerFilter === 'autocomplete' || column.definition.headerFilter === 'tickCross' || ((column.definition.editor === 'autocomplete' || column.definition.editor === 'tickCross') && column.definition.headerFilter === true) ) ) { editorElement.addEventListener("keyup", searchTrigger); editorElement.addEventListener("search", searchTrigger); //update number filtered columns on change if(column.modules.filter.attrType == "number"){ editorElement.addEventListener("change", function(e){ success(editorElement.value); }); } //change text inputs to search inputs to allow for clearing of field if(column.modules.filter.attrType == "text" && this.table.browser !== "ie"){ editorElement.setAttribute("type", "search"); // editorElement.off("change blur"); //prevent blur from triggering filter and preventing selection click } } //prevent input and select elements from propagating click to column sorters etc if(column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea"){ editorElement.addEventListener("mousedown",function(e){ e.stopPropagation(); }); } } filterElement.appendChild(editorElement); column.contentElement.appendChild(filterElement); if(!reinitialize){ self.headerFilterColumns.push(column); } } }else{ console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title); } } //hide all header filter elements (used to ensure correct column widths in "fitData" layout mode) hideHeaderFilterElements(){ this.headerFilterColumns.forEach(function(column){ if(column.modules.filter && column.modules.filter.headerElement){ column.modules.filter.headerElement.style.display = 'none'; } }); } //show all header filter elements (used to ensure correct column widths in "fitData" layout mode) showHeaderFilterElements(){ this.headerFilterColumns.forEach(function(column){ if(column.modules.filter && column.modules.filter.headerElement){ column.modules.filter.headerElement.style.display = ''; } }); } //programmatically set focus of header filter setHeaderFilterFocus(column){ if(column.modules.filter && column.modules.filter.headerElement){ column.modules.filter.headerElement.focus(); }else{ console.warn("Column Filter Focus Error - No header filter set on column:", column.getField()); } } //programmatically get value of header filter getHeaderFilterValue(column){ if(column.modules.filter && column.modules.filter.headerElement){ return column.modules.filter.value; } else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } //programmatically set value of header filter setHeaderFilterValue(column, value){ if (column){ if(column.modules.filter && column.modules.filter.headerElement){ this.generateHeaderFilterElement(column, value, true); column.modules.filter.success(value); }else{ console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } } reloadHeaderFilter(column){ if (column){ if(column.modules.filter && column.modules.filter.headerElement){ this.generateHeaderFilterElement(column, column.modules.filter.value, true); }else{ console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } } refreshFilter(){ if(this.tableInitialized){ if(this.table.options.filterMode === "remote"){ this.reloadData(null, false, false); }else{ this.refreshData(true); } } //TODO - Persist left position of row manager // left = this.scrollLeft; // this.scrollHorizontal(left); } //check if the filters has changed since last use trackChanges(){ this.changed = true; this.dispatch("filter-changed"); } //check if the filters has changed since last use hasChanged(){ var changed = this.changed; this.changed = false; return changed; } //set standard filters setFilter(field, type, value, params){ this.filterList = []; if(!Array.isArray(field)){ field = [{field:field, type:type, value:value, params:params}]; } this.addFilter(field); } //add filter to array addFilter(field, type, value, params){ var changed = false; if(!Array.isArray(field)){ field = [{field:field, type:type, value:value, params:params}]; } field.forEach((filter) => { filter = this.findFilter(filter); if(filter){ this.filterList.push(filter); changed = true; } }); if(changed){ this.trackChanges(); } } findFilter(filter){ var column; if(Array.isArray(filter)){ return this.findSubFilters(filter); } var filterFunc = false; if(typeof filter.field == "function"){ filterFunc = function(data){ return filter.field(data, filter.type || {});// pass params to custom filter function }; }else{ if(Filter.filters[filter.type]){ column = this.table.columnManager.getColumnByField(filter.field); if(column){ filterFunc = function(data){ return Filter.filters[filter.type](filter.value, column.getFieldValue(data), data, filter.params || {}); }; }else{ filterFunc = function(data){ return Filter.filters[filter.type](filter.value, data[filter.field], data, filter.params || {}); }; } }else{ console.warn("Filter Error - No such filter type found, ignoring: ", filter.type); } } filter.func = filterFunc; return filter.func ? filter : false; } findSubFilters(filters){ var output = []; filters.forEach((filter) => { filter = this.findFilter(filter); if(filter){ output.push(filter); } }); return output.length ? output : false; } //get all filters getFilters(all, ajax){ var output = []; if(all){ output = this.getHeaderFilters(); } if(ajax){ output.forEach(function(item){ if(typeof item.type == "function"){ item.type = "function"; } }); } output = output.concat(this.filtersToArray(this.filterList, ajax)); return output; } //filter to Object filtersToArray(filterList, ajax){ var output = []; filterList.forEach((filter) => { var item; if(Array.isArray(filter)){ output.push(this.filtersToArray(filter, ajax)); }else{ item = {field:filter.field, type:filter.type, value:filter.value}; if(ajax){ if(typeof item.type == "function"){ item.type = "function"; } } output.push(item); } }); return output; } //get all filters getHeaderFilters(){ var output = []; for(var key in this.headerFilters){ output.push({field:key, type:this.headerFilters[key].type, value:this.headerFilters[key].value}); } return output; } //remove filter from array removeFilter(field, type, value){ if(!Array.isArray(field)){ field = [{field:field, type:type, value:value}]; } field.forEach((filter) => { var index = -1; if(typeof filter.field == "object"){ index = this.filterList.findIndex((element) => { return filter === element; }); }else{ index = this.filterList.findIndex((element) => { return filter.field === element.field && filter.type === element.type && filter.value === element.value; }); } if(index > -1){ this.filterList.splice(index, 1); }else{ console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type); } }); this.trackChanges(); } //clear filters clearFilter(all){ this.filterList = []; if(all){ this.clearHeaderFilter(); } this.trackChanges(); } //clear header filters clearHeaderFilter(){ this.headerFilters = {}; this.prevHeaderFilterChangeCheck = "{}"; this.headerFilterColumns.forEach((column) => { if(typeof column.modules.filter.value !== "undefined"){ delete column.modules.filter.value; } column.modules.filter.prevSuccess = undefined; this.reloadHeaderFilter(column); }); this.trackChanges(); } //search data and return matching rows search (searchType, field, type, value){ var activeRows = [], filterList = []; if(!Array.isArray(field)){ field = [{field:field, type:type, value:value}]; } field.forEach((filter) => { filter = this.findFilter(filter); if(filter){ filterList.push(filter); } }); this.table.rowManager.rows.forEach((row) => { var match = true; filterList.forEach((filter) => { if(!this.filterRecurse(filter, row.getData())){ match = false; } }); if(match){ activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent()); } }); return activeRows; } //filter row array filter(rowList, filters){ var activeRows = [], activeRowComponents = []; if(this.subscribedExternal("dataFiltering")){ this.dispatchExternal("dataFiltering", this.getFilters(true)); } if(this.table.options.filterMode !== "remote" && (this.filterList.length || Object.keys(this.headerFilters).length)){ rowList.forEach((row) => { if(this.filterRow(row)){ activeRows.push(row); } }); }else{ activeRows = rowList.slice(0); } if(this.subscribedExternal("dataFiltered")){ activeRows.forEach((row) => { activeRowComponents.push(row.getComponent()); }); this.dispatchExternal("dataFiltered", this.getFilters(true), activeRowComponents); } return activeRows; } //filter individual row filterRow(row, filters){ var match = true, data = row.getData(); this.filterList.forEach((filter) => { if(!this.filterRecurse(filter, data)){ match = false; } }); for(var field in this.headerFilters){ if(!this.headerFilters[field].func(data)){ match = false; } } return match; } filterRecurse(filter, data){ var match = false; if(Array.isArray(filter)){ filter.forEach((subFilter) => { if(this.filterRecurse(subFilter, data)){ match = true; } }); }else{ match = filter.func(data); } return match; } } Filter.moduleName = "filter"; //load defaults Filter.filters = defaultFilters; export default Filter;