/* ------------------------------------------------------------------------ - HTML Table Filter Generator v0.0.1 - By Max Guglielmi (tablefilter.free.fr) - Licensed under the MIT License --------------------------------------------------------------------------- - Special credit to: Cedric Wartel, cnx.claude@free.fr, Florent Hirchy, Váry Péter, Anthony Maes, Nuovella Williams, Fuggerbit, Venkata Seshagiri Rao Raya, Piepiax, Manuel Kern, Baladhandayutham for active contribution and/or inspiration ------------------------------------------------------------------------ */ import Event from './event'; import Dom from './dom'; import Str from './string'; import Cookie from './cookie'; import Types from './types'; import Arr from './array'; import DateHelper from './date'; import Helpers from './helpers'; // Modules import {Store} from './modules/store'; import {GridLayout} from './modules/gridLayout'; import {Loader} from './modules/loader'; import {HighlightKeyword} from './modules/highlightKeywords'; import {PopupFilter} from './modules/popupFilter'; import {Dropdown} from './modules/dropdown'; import {CheckList} from './modules/checkList'; import {RowsCounter} from './modules/rowsCounter'; import {StatusBar} from './modules/statusBar'; import {Paging} from './modules/paging'; import {ClearButton} from './modules/clearButton'; import {Help} from './modules/help'; import {AlternateRows} from './modules/alternateRows'; import {ColOps} from './modules/colOps'; var global = window, isValidDate = DateHelper.isValid, formatDate = DateHelper.format, doc = global.document; export class TableFilter{ /** * TF object constructor * @param {String} id Table id * @param {Number} row index indicating the 1st row * @param {Object} configuration object * * TODO: Accept a TABLE element or query selectors */ constructor(id) { if(arguments.length === 0){ return; } this.id = id; this.version = '0.0.1'; this.year = new Date().getFullYear(); this.tbl = Dom.id(id); this.startRow = null; this.refRow = null; this.headersRow = null; this.cfg = {}; this.nbFilterableRows = null; this.nbRows = null; this.nbCells = null; this._hasGrid = false; this.enableModules = false; if(!this.tbl || Str.lower(this.tbl.nodeName) !== 'table' || this.getRowsNb() === 0){ throw new Error( 'Could not instantiate TableFilter class: ' + 'HTML table not found.'); } if(arguments.length > 1){ for(let i=0, len=arguments.length; i 1){ this.filtersRowIndex = this.headersRow+1; } else { this.filtersRowIndex = 1; this.headersRow = 0; } } //defines tag of the cells containing filters (td/th) this.fltCellTag = f.filters_cell_tag!=='th' || f.filters_cell_tag!=='td' ? 'td' : f.filters_cell_tag; //stores filters ids this.fltIds = []; //stores filters DOM elements this.fltElms = []; //stores filters values this.searchArgs = null; //stores table data this.tblData = []; //stores valid rows indexes (rows visible upon filtering) this.validRowsIndex = null; //stores filters row element this.fltGridEl = null; //is first load boolean this.isFirstLoad = true; //container div for paging elements, reset btn etc. this.infDiv = null; //div for rows counter this.lDiv = null; //div for reset button and results per page select this.rDiv = null; //div for paging elements this.mDiv = null; //table container div for fixed headers (IE only) this.contDiv = null; //defines css class for div containing paging elements, rows counter etc this.infDivCssClass = f.inf_div_css_class || 'inf'; //defines css class for left div this.lDivCssClass = f.left_div_css_class || 'ldiv'; //defines css class for right div this.rDivCssClass = f.right_div_css_class || 'rdiv'; //defines css class for mid div this.mDivCssClass = f.middle_div_css_class || 'mdiv'; //table container div css class this.contDivCssClass = f.content_div_css_class || 'cont'; /*** filters' grid appearance ***/ //stylesheet file this.stylesheet = f.stylesheet || this.basePath+'tablefilter.css'; this.stylesheetId = this.id + '_style'; //defines css class for filters row this.fltsRowCssClass = f.flts_row_css_class || 'fltrow'; //enables/disables icons (paging, reset button) this.enableIcons = f.enable_icons===false ? false : true; //enables/disbles rows alternating bg colors this.alternateBgs = Boolean(f.alternate_rows); //defines widths of columns this.hasColWidths = Types.isArray(f.col_widths); this.colWidths = this.hasColWidths ? f.col_widths : null; //tbody height if fixed headers enabled this.tBodyH = !isNaN(f.tbody_height) ? f.tbody_height : 200; //defines css class for filters this.fltCssClass = f.flt_css_class || 'flt'; //defines css class for multiple selects filters this.fltMultiCssClass = f.flt_multi_css_class || 'flt_multi'; //defines css class for filters this.fltSmallCssClass = f.flt_small_css_class || 'flt_s'; //defines css class for single-filter this.singleFltCssClass = f.single_flt_css_class || 'single_flt'; this.isStartBgAlternate = true; /*** filters' grid behaviours ***/ //enables/disables enter key this.enterKey = f.enter_key===false ? false : true; //calls function before filtering starts this.onBeforeFilter = Types.isFn(f.on_before_filter) ? f.on_before_filter : null; //calls function after filtering this.onAfterFilter = Types.isFn(f.on_after_filter) ? f.on_after_filter : null; //enables/disables case sensitivity this.caseSensitive = Boolean(f.case_sensitive); //enables/disbles exact match for search this.exactMatch = Boolean(f.exact_match); //refreshes drop-down lists upon validation this.linkedFilters = Boolean(f.linked_filters); //wheter excluded options are disabled this.disableExcludedOptions = Boolean(f.disable_excluded_options); //stores active filter element this.activeFlt = null; //id of active filter this.activeFilterId = null; //enables/disbles column operation(sum,mean) this.hasColOperation = Boolean(f.col_operation); this.colOperation = null; //enables always visible rows this.hasVisibleRows = Boolean(f.rows_always_visible); //array containing always visible rows this.visibleRows = this.hasVisibleRows ? f.rows_always_visible : []; //defines search type: include or exclude this.searchType = f.search_type || 'include'; //enables/disables external filters generation this.isExternalFlt = Boolean(f.external_flt_grid); //array containing ids of external elements containing filters this.externalFltTgtIds = f.external_flt_grid_ids || null; //stores filters elements if isExternalFlt is true this.externalFltEls = []; //delays any filtering process if loader true this.execDelay = !isNaN(f.exec_delay) ? parseInt(f.exec_delay,10) : 100; //calls function when filters grid loaded this.onFiltersLoaded = Types.isFn(f.on_filters_loaded) ? f.on_filters_loaded : null; //enables/disables single filter search this.singleSearchFlt = Boolean(f.single_search_filter); //calls function after row is validated this.onRowValidated = Types.isFn(f.on_row_validated) ? f.on_row_validated : null; //array defining columns for customCellData event this.customCellDataCols = f.custom_cell_data_cols ? f.custom_cell_data_cols : []; //calls custom function for retrieving cell data this.customCellData = Types.isFn(f.custom_cell_data) ? f.custom_cell_data : null; //input watermark text array this.watermark = f.watermark || ''; this.isWatermarkArray = Types.isArray(this.watermark); //id of toolbar container element this.toolBarTgtId = f.toolbar_target_id || null; //enables/disables help div this.helpInstructions = Types.isUndef(f.help_instructions) ? undefined : Boolean(f.help_instructions); //popup filters this.popUpFilters = Boolean(f.popup_filters); //active columns color this.markActiveColumns = Boolean(f.mark_active_columns); //defines css class for active column header this.activeColumnsCssClass = f.active_columns_css_class || 'activeHeader'; //calls function before active column header is marked this.onBeforeActiveColumn = Types.isFn(f.on_before_active_column) ? f.on_before_active_column : null; //calls function after active column header is marked this.onAfterActiveColumn = Types.isFn(f.on_after_active_column) ? f.on_after_active_column : null; /*** select filter's customisation and behaviours ***/ //defines 1st option text this.displayAllText = f.display_all_text || ''; //enables/disables empty option in combo-box filters this.enableEmptyOption = Boolean(f.enable_empty_option); //defines empty option text this.emptyText = f.empty_text || '(Empty)'; //enables/disables non empty option in combo-box filters this.enableNonEmptyOption = Boolean(f.enable_non_empty_option); //defines empty option text this.nonEmptyText = f.non_empty_text || '(Non empty)'; //enables/disables onChange event on combo-box this.onSlcChange = f.on_change===false ? false : true; //enables/disables select options sorting this.sortSlc = f.sort_select===false ? false : true; //enables/disables ascending numeric options sorting this.isSortNumAsc = Boolean(f.sort_num_asc); this.sortNumAsc = this.isSortNumAsc ? f.sort_num_asc : null; //enables/disables descending numeric options sorting this.isSortNumDesc = Boolean(f.sort_num_desc); this.sortNumDesc = this.isSortNumDesc ? f.sort_num_desc : null; //enabled selects are populated on demand this.fillSlcOnDemand = Boolean(f.fill_slc_on_demand); this.hasCustomOptions = Types.isObj(f.custom_options); this.customOptions = f.custom_options; /*** Filter operators ***/ this.rgxOperator = f.regexp_operator || 'rgx:'; this.emOperator = f.empty_operator || '[empty]'; this.nmOperator = f.nonempty_operator || '[nonempty]'; this.orOperator = f.or_operator || '||'; this.anOperator = f.and_operator || '&&'; this.grOperator = f.greater_operator || '>'; this.lwOperator = f.lower_operator || '<'; this.leOperator = f.lower_equal_operator || '<='; this.geOperator = f.greater_equal_operator || '>='; this.dfOperator = f.different_operator || '!'; this.lkOperator = f.like_operator || '*'; this.eqOperator = f.equal_operator || '='; this.stOperator = f.start_with_operator || '{'; this.enOperator = f.end_with_operator || '}'; this.curExp = f.cur_exp || '^[¥£€$]'; this.separator = f.separator || ','; /*** rows counter ***/ //show/hides rows counter this.rowsCounter = Boolean(f.rows_counter); /*** status bar ***/ //show/hides status bar this.statusBar = Boolean(f.status_bar); /*** loader ***/ //enables/disables loader/spinner indicator this.loader = Boolean(f.loader); /*** validation - reset buttons/links ***/ //show/hides filter's validation button this.displayBtn = Boolean(f.btn); //defines validation button text this.btnText = f.btn_text || (!this.enableIcons ? 'Go' : ''); //defines css class for validation button this.btnCssClass = f.btn_css_class || (!this.enableIcons ? 'btnflt' : 'btnflt_icon'); //show/hides reset link this.btnReset = Boolean(f.btn_reset); //defines css class for reset button this.btnResetCssClass = f.btn_reset_css_class || 'reset'; //callback function before filters are cleared this.onBeforeReset = Types.isFn(f.on_before_reset) ? f.on_before_reset : null; //callback function after filters are cleared this.onAfterReset = Types.isFn(f.on_after_reset) ? f.on_after_reset : null; /*** paging ***/ //enables/disables table paging this.paging = Boolean(f.paging); this.nbVisibleRows = 0; //nb visible rows this.nbHiddenRows = 0; //nb hidden rows /*** webfx sort adapter ***/ //enables/disables default table sorting this.sort = Boolean(f.sort); //indicates if sort is set (used in tfAdapter.sortabletable.js) this.isSortEnabled = false; this.sortConfig = f.sort_config || {}; this.sortConfig.name = this.sortConfig.name || 'sort'; this.sortConfig.path = this.sortConfig.path || null; this.sortConfig.sortTypes = Types.isArray(this.sortConfig.sort_types) ? this.sortConfig.sort_types : []; this.sortConfig.sortCol = this.sortConfig.sort_col || null; this.sortConfig.asyncSort = Boolean(this.sortConfig.async_sort); this.sortConfig.triggerIds = Types.isArray(this.sortConfig.sort_trigger_ids) ? this.sortConfig.sort_trigger_ids : []; /*** onkeyup event ***/ //enables/disables auto filtering, table is filtered when user stops //typing this.autoFilter = Boolean(f.auto_filter); //onkeyup delay timer (msecs) this.autoFilterDelay = !isNaN(f.auto_filter_delay) ? f.auto_filter_delay : 900; //typing indicator this.isUserTyping = null; this.autoFilterTimer = null; /*** keyword highlighting ***/ //enables/disables keyword highlighting this.highlightKeywords = Boolean(f.highlight_keywords); /*** data types ***/ //defines default date type (european DMY) this.defaultDateType = f.default_date_type || 'DMY'; //defines default thousands separator //US = ',' EU = '.' this.thousandsSeparator = f.thousands_separator || ','; //defines default decimal separator //US & javascript = '.' EU = ',' this.decimalSeparator = f.decimal_separator || '.'; //enables number format per column this.hasColNbFormat = Boolean(f.col_number_format); //array containing columns nb formats this.colNbFormat = Types.isArray(this.hasColNbFormat) ? f.col_number_format : null; //enables date type per column this.hasColDateType = Types.isArray(f.col_date_type); //array containing columns date type this.colDateType = this.hasColDateType ? f.col_date_type : null; /*** status messages ***/ //filtering this.msgFilter = f.msg_filter || 'Filtering data...'; //populating drop-downs this.msgPopulate = f.msg_populate || 'Populating filter...'; //populating drop-downs this.msgPopulateCheckList = f.msg_populate_checklist || 'Populating list...'; //changing paging page this.msgChangePage = f.msg_change_page || 'Collecting paging data...'; //clearing filters this.msgClear = f.msg_clear || 'Clearing filters...'; //changing nb results/page this.msgChangeResults = f.msg_change_results || 'Changing results per page...'; //re-setting grid values this.msgResetValues = f.msg_reset_grid_values || 'Re-setting filters values...'; //re-setting page this.msgResetPage = f.msg_reset_page || 'Re-setting page...'; //re-setting page length this.msgResetPageLength = f.msg_reset_page_length || 'Re-setting page length...'; //table sorting this.msgSort = f.msg_sort || 'Sorting data...'; //extensions loading this.msgLoadExtensions = f.msg_load_extensions || 'Loading extensions...'; //themes loading this.msgLoadThemes = f.msg_load_themes || 'Loading theme(s)...'; /*** ids prefixes ***/ //css class name added to table this.prfxTf = 'TF'; //filters (inputs - selects) this.prfxFlt = 'flt'; //validation button this.prfxValButton = 'btn'; //container div for paging elements, rows counter etc. this.prfxInfDiv = 'inf_'; //left div this.prfxLDiv = 'ldiv_'; //right div this.prfxRDiv = 'rdiv_'; //middle div this.prfxMDiv = 'mdiv_'; //filter values cookie this.prfxCookieFltsValues = 'tf_flts_'; //page nb cookie this.prfxCookiePageNb = 'tf_pgnb_'; //page length cookie this.prfxCookiePageLen = 'tf_pglen_'; /*** cookies ***/ this.hasStoredValues = false; //remembers filters values on page load this.rememberGridValues = Boolean(f.remember_grid_values); //cookie storing filter values this.fltsValuesCookie = this.prfxCookieFltsValues + this.id; //remembers page nb on page load this.rememberPageNb = this.paging && f.remember_page_number; //cookie storing page nb this.pgNbCookie = this.prfxCookiePageNb + this.id; //remembers page length on page load this.rememberPageLen = this.paging && f.remember_page_length; //cookie storing page length this.pgLenCookie = this.prfxCookiePageLen + this.id; /*** extensions ***/ //imports external script this.extensions = f.extensions; this.hasExtensions = Types.isArray(this.extensions); /*** themes ***/ this.enableDefaultTheme = Boolean(f.enable_default_theme); //imports themes this.hasThemes = (this.enableDefaultTheme || Types.isArray(f.themes)); this.themes = f.themes || []; //themes path this.themesPath = f.themes_path || this.basePath + 'themes/'; // Features registry this.Cpt = { loader: null, alternateRows: null, colOps: null, rowsCounter: null, gridLayout: null, store: null, highlightKeywords: null, paging: null, checkList: null, dropdown: null, popupFilter: null, clearButton: null, help: null, statusBar: null }; // Extensions registry this.ExtRegistry = {}; /*** TF events ***/ // let o = this; this.Evt = { name: { filter: 'Filter', dropdown: 'dropdown', checklist: 'checkList', changepage: 'changePage', clear: 'Clear', changeresultsperpage: 'changeResults', resetvalues: 'ResetValues', resetpage: 'resetPage', resetpagelength: 'resetPageLength', sort: 'Sort', loadextensions: 'LoadExtensions', loadthemes: 'loadThemes' }, /*==================================================== - Detects key for a given element =====================================================*/ detectKey(e) { if(!this.enterKey){ return; } let _ev = e || global.event; if(_ev){ let key = Event.keyCode(_ev); if(key===13){ this.filter(); Event.cancel(_ev); Event.stop(_ev); } else { this.isUserTyping = true; global.clearInterval(this.autoFilterTimer); this.autoFilterTimer = null; } } }, /*==================================================== - auto filter for text filters =====================================================*/ onKeyUp(e) { if(!this.autoFilter){ return; } let _ev = e || global.event; let key = Event.keyCode(_ev); this.isUserTyping = false; function filter() { /*jshint validthis:true */ global.clearInterval(this.autoFilterTimer); this.autoFilterTimer = null; if(!this.isUserTyping){ this.filter(); this.isUserTyping = null; } } if(key!==13 && key!==9 && key!==27 && key!==38 && key!==40) { if(this.autoFilterTimer === null){ this.autoFilterTimer = global.setInterval( filter.bind(this), this.autoFilterDelay); } } else { global.clearInterval(this.autoFilterTimer); this.autoFilterTimer = null; } }, /*==================================================== - onkeydown event for input filters =====================================================*/ onKeyDown() { if(!this.autoFilter) { return; } this.isUserTyping = true; }, /*==================================================== - onblur event for input filters =====================================================*/ onInpBlur() { if(this.autoFilter){ this.isUserTyping = false; global.clearInterval(this.autoFilterTimer); } // if(o.ezEditTable){ // if(o.editable){ // o.ezEditTable.Editable.Set(); // } // if(o.selectable){ // o.ezEditTable.Selection.Set(); // } // } }, /*==================================================== - onfocus event for input filters =====================================================*/ onInpFocus(e) { let _ev = e || global.event; let elm = Event.target(_ev); this.activeFilterId = elm.getAttribute('id'); this.activeFlt = Dom.id(this.activeFilterId); if(this.popUpFilters){ Event.cancel(_ev); Event.stop(_ev); } // if(o.ezEditTable){ // if(o.editable){ // o.ezEditTable.Editable.Remove(); // } // if(o.selectable){ // o.ezEditTable.Selection.Remove(); // } // } }, /*==================================================== - onfocus event for select filters =====================================================*/ onSlcFocus(e) { let _ev = e || global.event; let elm = Event.target(_ev); this.activeFilterId = elm.getAttribute('id'); this.activeFlt = Dom.id(this.activeFilterId); // select is populated when element has focus if(this.fillSlcOnDemand && elm.getAttribute('filled') === '0'){ let ct = elm.getAttribute('ct'); this.Cpt.dropdown._build(ct); } if(this.popUpFilters){ Event.cancel(_ev); Event.stop(_ev); } }, /*==================================================== - onchange event for select filters =====================================================*/ onSlcChange(e) { if(!this.activeFlt){ return; } // let colIndex = o.activeFlt.getAttribute('colIndex'); //Checks filter is a checklist and caller is not null // if(o.activeFlt && colIndex && // o['col'+colIndex]===o.fltTypeCheckList && // !o.Evt.onSlcChange.caller){ return; } let _ev = e || global.event; if(this.popUpFilters){ Event.stop(_ev); } if(this.onSlcChange){ this.filter(); } }, /*==================================================== - onclick event for checklist filters =====================================================*/ onCheckListClick(e) { let _ev = e || global.event; let elm = Event.target(_ev); if(this.fillSlcOnDemand && elm.getAttribute('filled') === '0'){ let ct = elm.getAttribute('ct'); this.Cpt.checkList._build(ct); this.Cpt.checkList.checkListDiv[ct].onclick = null; this.Cpt.checkList.checkListDiv[ct].title = ''; } }, /*==================================================== - onclick event for validation button (btn property) =====================================================*/ onBtnClick() { this.filter(); } }; } /** * Initialise filtering grid bar behaviours and layout * * TODO: decompose in smaller methods */ init(){ if(this._hasGrid){ return; } if(!this.tbl){ this.tbl = Dom.id(this.id); } if(this.gridLayout){ this.refRow = this.startRow===null ? 0 : this.startRow; } if(this.popUpFilters && ((this.filtersRowIndex === 0 && this.headersRow === 1) || this.gridLayout)){ this.headersRow = 0; } let n = this.singleSearchFlt ? 1 : this.nbCells, inpclass; // if(global['tf_'+this.id] === undefined){ // global['tf_'+this.id] = this; // } //loads stylesheet if not imported this.import(this.stylesheetId, this.stylesheet, null, 'link'); //loads theme if(this.hasThemes){ this._loadThemes(); } if(this.rememberGridValues || this.rememberPageNb || this.rememberPageLen){ this.Cpt.store = new Store(this); } if(this.gridLayout){ this.Cpt.gridLayout = new GridLayout(this); this.Cpt.gridLayout.init(); } if(this.loader){ if(!this.Cpt.loader){ this.Cpt.loader = new Loader(this); } } if(this.highlightKeywords){ this.Cpt.highlightKeyword = new HighlightKeyword(this); } if(this.popUpFilters){ if(!this.Cpt.popupFilter){ this.Cpt.popupFilter = new PopupFilter(this); } this.Cpt.popupFilter.init(); } //filters grid is not generated if(!this.fltGrid){ this.refRow = this.refRow-1; if(this.gridLayout){ this.refRow = 0; } this.nbFilterableRows = this.getRowsNb(); this.nbVisibleRows = this.nbFilterableRows; this.nbRows = this.nbFilterableRows + this.refRow; } else { if(this.isFirstLoad){ let fltrow; if(!this.gridLayout){ let thead = Dom.tag(this.tbl, 'thead'); if(thead.length > 0){ fltrow = thead[0].insertRow(this.filtersRowIndex); } else { fltrow = this.tbl.insertRow(this.filtersRowIndex); } if(this.headersRow > 1 && this.filtersRowIndex <= this.headersRow && !this.popUpFilters){ this.headersRow++; } if(this.popUpFilters){ this.headersRow++; } fltrow.className = this.fltsRowCssClass; //Disable for grid_layout if(this.isExternalFlt && (!this.gridLayout || this.popUpFilters)){ fltrow.style.display = 'none'; } } this.nbFilterableRows = this.getRowsNb(); this.nbVisibleRows = this.nbFilterableRows; this.nbRows = this.tbl.rows.length; for(let i=0; i { let inst = new mod(this, ext); inst.init(); this.ExtRegistry[name] = inst; }); } /** * Destroy all the extensions defined in the configuration object */ destroyExtensions(){ let exts = this.extensions; for(let i=0, len=exts.length; i'; //Paging buttons this.btnPrevPageHtml = ''; this.btnNextPageHtml = ''; this.btnFirstPageHtml = ''; this.btnLastPageHtml = ''; //Loader this.loader = true; this.loaderHtml = '
'; this.loaderText = null; } /** * Destroy filter grid */ destroy(){ if(!this._hasGrid){ return; } let rows = this.tbl.rows, Cpt = this.Cpt; // if(this.paging){ // Cpt.paging.destroy(); // } // if(this.statusBar){ // Cpt.statusBar.destroy(); // } // if(this.rowsCounter){ // Cpt.rowsCounter.destroy(); // } // if(this.btnReset){ // Cpt.clearButton.destroy(); // } // if(this.helpInstructions){ // Cpt.help.destroy(); // } if(this.isExternalFlt && !this.popUpFilters){ this.removeExternalFlts(); } if(this.infDiv){ this.removeToolbar(); } if(this.highlightKeywords){ Cpt.highlightKeyword.unhighlightAll(); } // if(this.loader){ // Cpt.loader.destroy(); // } // if(this.popUpFilters){ // Cpt.popupFilter.destroy(); // } if(this.markActiveColumns){ this.clearActiveColumns(); } if(this.hasExtensions){ this.destroyExtensions(); } //this loop shows all rows and removes validRow attribute for(let j=this.refRow; j