mirror of
https://github.com/koalyptus/TableFilter.git
synced 2024-06-08 08:52:32 +02:00
462 lines
13 KiB
JavaScript
462 lines
13 KiB
JavaScript
import {Feature} from '../feature';
|
|
import {isUndef, EMPTY_FN} from '../types';
|
|
import {createElm, removeElm} from '../dom';
|
|
import {addEvt, cancelEvt, stopEvt, targetEvt, removeEvt} from '../event';
|
|
import {INPUT, NONE, CHECKLIST, MULTIPLE} from '../const';
|
|
import {root} from '../root';
|
|
import {defaultsStr, defaultsBool, defaultsArr, defaultsFn} from '../settings';
|
|
|
|
/**
|
|
* Pop-up filter component
|
|
* @export
|
|
* @class PopupFilter
|
|
* @extends {Feature}
|
|
*/
|
|
export class PopupFilter extends Feature {
|
|
|
|
/**
|
|
* Creates an instance of PopupFilter
|
|
* @param {TableFilter} tf TableFilter instance
|
|
*/
|
|
constructor(tf) {
|
|
super(tf, PopupFilter);
|
|
|
|
// Configuration object
|
|
let f = this.config.popup_filters || {};
|
|
|
|
/**
|
|
* Close active popup filter upon filtering, enabled by default
|
|
* @type {Boolean}
|
|
*/
|
|
this.closeOnFiltering = defaultsBool(f.close_on_filtering, true);
|
|
|
|
/**
|
|
* Filter icon path
|
|
* @type {String}
|
|
*/
|
|
this.iconPath = defaultsStr(f.image, tf.themesPath + 'icn_filter.gif');
|
|
|
|
/**
|
|
* Active filter icon path
|
|
* @type {string}
|
|
*/
|
|
this.activeIconPath = defaultsStr(f.image_active,
|
|
tf.themesPath + 'icn_filterActive.gif');
|
|
|
|
/**
|
|
* HTML for the filter icon
|
|
* @type {string}
|
|
*/
|
|
this.iconHtml = defaultsStr(f.image_html,
|
|
'<img src="' + this.iconPath + '" alt="Column filter" />');
|
|
|
|
/**
|
|
* Css class assigned to the popup container element
|
|
* @type {String}
|
|
*/
|
|
this.placeholderCssClass = defaultsStr(f.placeholder_css_class,
|
|
'popUpPlaceholder');
|
|
|
|
/**
|
|
* Css class assigned to filter container element
|
|
* @type {String}
|
|
*/
|
|
this.containerCssClass = defaultsStr(f.div_css_class, 'popUpFilter');
|
|
|
|
/**
|
|
* Ensure filter's container element width matches column width, enabled
|
|
* by default
|
|
* @type {Boolean}
|
|
*/
|
|
this.adjustToContainer = defaultsBool(f.adjust_to_container, true);
|
|
|
|
/**
|
|
* Callback fired before a popup filter is opened
|
|
* @type {Function}
|
|
*/
|
|
this.onBeforeOpen = defaultsFn(f.on_before_popup_filter_open, EMPTY_FN);
|
|
|
|
/**
|
|
* Callback fired after a popup filter is opened
|
|
* @type {Function}
|
|
*/
|
|
this.onAfterOpen = defaultsFn(f.on_after_popup_filter_open, EMPTY_FN);
|
|
|
|
/**
|
|
* Callback fired before a popup filter is closed
|
|
* @type {Function}
|
|
*/
|
|
this.onBeforeClose = defaultsFn(f.on_before_popup_filter_close,
|
|
EMPTY_FN);
|
|
|
|
/**
|
|
* Callback fired after a popup filter is closed
|
|
* @type {Function}
|
|
*/
|
|
this.onAfterClose = defaultsFn(f.on_after_popup_filter_close, EMPTY_FN);
|
|
|
|
/**
|
|
* Collection of filters spans
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this.fltSpans = [];
|
|
|
|
/**
|
|
* Collection of filters icons
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this.fltIcons = [];
|
|
|
|
/**
|
|
* Collection of filters icons cached after pop-up filters are removed
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this.filtersCache = null;
|
|
|
|
/**
|
|
* Collection of filters containers
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
this.fltElms = defaultsArr(this.filtersCache, []);
|
|
|
|
/**
|
|
* Prefix for pop-up filter container ID
|
|
* @type {String}
|
|
* @private
|
|
*/
|
|
this.prfxDiv = 'popup_';
|
|
|
|
/**
|
|
* Column index of popup filter currently active
|
|
* @type {Number}
|
|
* @private
|
|
*/
|
|
this.activeFilterIdx = -1;
|
|
}
|
|
|
|
/**
|
|
* Click event handler for pop-up filter icon
|
|
* @private
|
|
*/
|
|
onClick(evt) {
|
|
let elm = targetEvt(evt).parentNode;
|
|
let colIndex = parseInt(elm.getAttribute('ci'), 10);
|
|
|
|
this.closeAll(colIndex);
|
|
this.toggle(colIndex);
|
|
|
|
if (this.adjustToContainer) {
|
|
let cont = this.fltElms[colIndex],
|
|
header = this.tf.getHeaderElement(colIndex),
|
|
headerWidth = header.clientWidth * 0.95;
|
|
cont.style.width = parseInt(headerWidth, 10) + 'px';
|
|
}
|
|
cancelEvt(evt);
|
|
stopEvt(evt);
|
|
}
|
|
|
|
/**
|
|
* Mouse-up event handler handling popup filter auto-close behaviour
|
|
* @private
|
|
*/
|
|
onMouseup(evt) {
|
|
if (this.activeFilterIdx === -1) {
|
|
return;
|
|
}
|
|
let targetElm = targetEvt(evt);
|
|
let activeFlt = this.fltElms[this.activeFilterIdx];
|
|
let icon = this.fltIcons[this.activeFilterIdx];
|
|
|
|
if (icon === targetElm) {
|
|
return;
|
|
}
|
|
|
|
while (targetElm && targetElm !== activeFlt) {
|
|
targetElm = targetElm.parentNode;
|
|
}
|
|
|
|
if (targetElm !== activeFlt) {
|
|
this.close(this.activeFilterIdx);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Initialize DOM elements
|
|
*/
|
|
init() {
|
|
if (this.initialized) {
|
|
return;
|
|
}
|
|
|
|
let tf = this.tf;
|
|
|
|
// Enable external filters
|
|
tf.externalFltIds = [''];
|
|
|
|
// Override filters row index supplied by configuration
|
|
tf.filtersRowIndex = 0;
|
|
|
|
// Override headers row index if no grouped headers
|
|
// TODO: Because of the filters row generation, headers row index needs
|
|
// adjusting: prevent useless row generation
|
|
if (tf.headersRow <= 1 && isNaN(tf.config().headers_row_index)) {
|
|
tf.headersRow = 0;
|
|
}
|
|
|
|
// Adjust headers row index for grid-layout mode
|
|
// TODO: Because of the filters row generation, headers row index needs
|
|
// adjusting: prevent useless row generation
|
|
if (tf.gridLayout) {
|
|
tf.headersRow--;
|
|
this.buildIcons();
|
|
}
|
|
|
|
// subscribe to events
|
|
this.emitter.on(['before-filtering'], () => this.setIconsState());
|
|
this.emitter.on(['after-filtering'], () => this.closeAll());
|
|
this.emitter.on(['cell-processed'],
|
|
(tf, cellIndex) => this.changeState(cellIndex, true));
|
|
this.emitter.on(['filters-row-inserted'], () => this.buildIcons());
|
|
this.emitter.on(['before-filter-init'],
|
|
(tf, colIndex) => this.build(colIndex));
|
|
|
|
/** @inherited */
|
|
this.initialized = true;
|
|
}
|
|
|
|
/**
|
|
* Reset previously destroyed feature
|
|
*/
|
|
reset() {
|
|
this.enable();
|
|
this.init();
|
|
this.buildIcons();
|
|
this.buildAll();
|
|
}
|
|
|
|
/**
|
|
* Build all filters icons
|
|
*/
|
|
buildIcons() {
|
|
let tf = this.tf;
|
|
|
|
// TODO: Because of the filters row generation, headers row index needs
|
|
// adjusting: prevent useless row generation
|
|
tf.headersRow++;
|
|
|
|
tf.eachCol(
|
|
(i) => {
|
|
let icon = createElm('span', ['ci', i]);
|
|
icon.innerHTML = this.iconHtml;
|
|
let header = tf.getHeaderElement(i);
|
|
header.appendChild(icon);
|
|
addEvt(icon, 'click', (evt) => this.onClick(evt));
|
|
this.fltSpans[i] = icon;
|
|
this.fltIcons[i] = icon.firstChild;
|
|
},
|
|
// continue condition function
|
|
(i) => tf.getFilterType(i) === NONE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Build all pop-up filters elements
|
|
*/
|
|
buildAll() {
|
|
for (let i = 0; i < this.filtersCache.length; i++) {
|
|
this.build(i, this.filtersCache[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build a specified pop-up filter elements
|
|
* @param {Number} colIndex Column index
|
|
* @param {Object} div Optional container DOM element
|
|
*/
|
|
build(colIndex, div) {
|
|
let tf = this.tf;
|
|
let contId = `${this.prfxDiv}${tf.id}_${colIndex}`;
|
|
let placeholder = createElm('div', ['class', this.placeholderCssClass]);
|
|
let cont = div ||
|
|
createElm('div', ['id', contId], ['class', this.containerCssClass]);
|
|
tf.externalFltIds[colIndex] = cont.id;
|
|
placeholder.appendChild(cont);
|
|
|
|
let header = tf.getHeaderElement(colIndex);
|
|
header.insertBefore(placeholder, header.firstChild);
|
|
addEvt(cont, 'click', (evt) => stopEvt(evt));
|
|
this.fltElms[colIndex] = cont;
|
|
}
|
|
|
|
/**
|
|
* Toggle visibility of specified filter
|
|
* @param {Number} colIndex Column index
|
|
*/
|
|
toggle(colIndex) {
|
|
if (!this.isOpen(colIndex)) {
|
|
this.open(colIndex);
|
|
} else {
|
|
this.close(colIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open popup filter of specified column
|
|
* @param {Number} colIndex Column index
|
|
*/
|
|
open(colIndex) {
|
|
let tf = this.tf,
|
|
container = this.fltElms[colIndex];
|
|
|
|
this.onBeforeOpen(this, container, colIndex);
|
|
|
|
container.style.display = 'block';
|
|
this.activeFilterIdx = colIndex;
|
|
addEvt(root, 'mouseup', (evt) => this.onMouseup(evt));
|
|
|
|
if (tf.getFilterType(colIndex) === INPUT) {
|
|
let flt = tf.getFilterElement(colIndex);
|
|
if (flt) {
|
|
flt.focus();
|
|
}
|
|
}
|
|
|
|
this.onAfterOpen(this, container, colIndex);
|
|
}
|
|
|
|
/**
|
|
* Close popup filter of specified column
|
|
* @param {Number} colIndex Column index
|
|
*/
|
|
close(colIndex) {
|
|
let container = this.fltElms[colIndex];
|
|
|
|
this.onBeforeClose(this, container, colIndex);
|
|
|
|
container.style.display = NONE;
|
|
if (this.activeFilterIdx === colIndex) {
|
|
this.activeFilterIdx = -1;
|
|
}
|
|
removeEvt(root, 'mouseup', (evt) => this.onMouseup(evt));
|
|
|
|
this.onAfterClose(this, container, colIndex);
|
|
}
|
|
|
|
/**
|
|
* Check if popup filter for specified column is open
|
|
* @param {Number} colIndex Column index
|
|
* @returns {Boolean}
|
|
*/
|
|
isOpen(colIndex) {
|
|
return this.fltElms[colIndex].style.display === 'block';
|
|
}
|
|
|
|
/**
|
|
* Close all filters excepted for the specified one if any
|
|
* @param {Number} exceptIdx Column index of the filter to not close
|
|
*/
|
|
closeAll(exceptIdx) {
|
|
// Do not close filters only if argument is undefined and close on
|
|
// filtering option is disabled
|
|
if (isUndef(exceptIdx) && !this.closeOnFiltering) {
|
|
return;
|
|
}
|
|
for (let i = 0; i < this.fltElms.length; i++) {
|
|
if (i === exceptIdx) {
|
|
continue;
|
|
}
|
|
let fltType = this.tf.getFilterType(i);
|
|
let isMultipleFilter =
|
|
(fltType === CHECKLIST || fltType === MULTIPLE);
|
|
|
|
// Always hide all single selection filter types but hide multiple
|
|
// selection filter types only if index set
|
|
if (!isMultipleFilter || !isUndef(exceptIdx)) {
|
|
this.close(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build all the icons representing the pop-up filters
|
|
*/
|
|
setIconsState() {
|
|
for (let i = 0; i < this.fltIcons.length; i++) {
|
|
this.changeState(i, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply specified icon state
|
|
* @param {Number} colIndex Column index
|
|
* @param {Boolean} active Apply active state
|
|
*/
|
|
changeState(colIndex, active) {
|
|
let icon = this.fltIcons[colIndex];
|
|
if (icon) {
|
|
icon.src = active ? this.activeIconPath : this.iconPath;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove pop-up filters
|
|
*/
|
|
destroy() {
|
|
if (!this.initialized) {
|
|
return;
|
|
}
|
|
|
|
this.filtersCache = [];
|
|
for (let i = 0; i < this.fltElms.length; i++) {
|
|
let container = this.fltElms[i],
|
|
placeholder = container.parentNode,
|
|
icon = this.fltSpans[i],
|
|
iconImg = this.fltIcons[i];
|
|
if (container) {
|
|
removeElm(container);
|
|
this.filtersCache[i] = container;
|
|
}
|
|
container = null;
|
|
if (placeholder) {
|
|
removeElm(placeholder);
|
|
}
|
|
placeholder = null;
|
|
if (icon) {
|
|
removeElm(icon);
|
|
}
|
|
icon = null;
|
|
if (iconImg) {
|
|
removeElm(iconImg);
|
|
}
|
|
iconImg = null;
|
|
}
|
|
this.fltElms = [];
|
|
this.fltSpans = [];
|
|
this.fltIcons = [];
|
|
|
|
// TODO: expose an API to handle external filter IDs
|
|
this.tf.externalFltIds = [];
|
|
|
|
// unsubscribe to events
|
|
this.emitter.off(['before-filtering'], () => this.setIconsState());
|
|
this.emitter.off(['after-filtering'], () => this.closeAll());
|
|
this.emitter.off(['cell-processed'],
|
|
(tf, cellIndex) => this.changeState(cellIndex, true));
|
|
this.emitter.off(['filters-row-inserted'], () => this.buildIcons());
|
|
this.emitter.off(['before-filter-init'],
|
|
(tf, colIndex) => this.build(colIndex));
|
|
|
|
this.initialized = false;
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: remove as soon as feature name is fixed
|
|
PopupFilter.meta = {altName: 'popupFilters'};
|