1
0
Fork 0
mirror of https://github.com/koalyptus/TableFilter.git synced 2024-05-20 15:26:40 +02:00
TableFilter/src/modules/checkList.js

489 lines
17 KiB
JavaScript

import {Feature} from './feature';
import Dom from '../dom';
import Arr from '../array';
import Str from '../string';
import Sort from '../sort';
import Event from '../event';
import Types from '../types';
import {CHECKLIST, NONE} from '../const';
const SORT_ERROR = 'Filter options for column {0} cannot be sorted in ' +
'{1} manner.';
export class CheckList extends Feature {
/**
* Checklist UI component
* @param {Object} tf TableFilter instance
*/
constructor(tf) {
super(tf, 'checkList');
// Configuration object
let f = tf.config();
this.checkListDiv = []; //checklist container div
//defines css class for div containing checklist filter
this.checkListDivCssClass = f.div_checklist_css_class ||
'div_checklist';
//defines css class for checklist filters
this.checkListCssClass = f.checklist_css_class || 'flt_checklist';
//defines css class for checklist item (li)
this.checkListItemCssClass = f.checklist_item_css_class ||
'flt_checklist_item';
//defines css class for selected checklist item (li)
this.checkListSlcItemCssClass = f.checklist_selected_item_css_class ||
'flt_checklist_slc_item';
//Load on demand text
this.activateCheckListTxt = f.activate_checklist_text ||
'Click to load filter data';
//defines css class for checklist filters
this.checkListItemDisabledCssClass =
f.checklist_item_disabled_css_class ||
'flt_checklist_item_disabled';
this.enableCheckListResetFilter =
f.enable_checklist_reset_filter === false ? false : true;
//checklist filter container div
this.prfxCheckListDiv = 'chkdiv_';
this.isCustom = null;
this.opts = null;
this.optsTxt = null;
this.excludedOpts = null;
}
onChange(evt) {
let elm = Event.target(evt);
let tf = this.tf;
this.emitter.emit('filter-focus', tf, elm);
tf.filter();
}
optionClick(evt) {
this.setCheckListValues(evt.target);
this.onChange(evt);
}
onCheckListClick(evt) {
let elm = Event.target(evt);
if (this.tf.loadFltOnDemand && elm.getAttribute('filled') === '0') {
let ct = elm.getAttribute('ct');
let div = this.checkListDiv[ct];
this.build(ct);
Event.remove(div, 'click', (evt) => this.onCheckListClick(evt));
}
}
/**
* Initialize checklist filter
* @param {Number} colIndex Column index
* @param {Boolean} isExternal External filter flag
* @param {DOMElement} container Dom element containing the filter
*/
init(colIndex, isExternal, container) {
let tf = this.tf;
let externalFltTgtId = isExternal ?
tf.externalFltTgtIds[colIndex] : null;
let divCont = Dom.create('div',
['id', this.prfxCheckListDiv + colIndex + '_' + tf.id],
['ct', colIndex], ['filled', '0']);
divCont.className = this.checkListDivCssClass;
//filter is appended in desired element
if (externalFltTgtId) {
Dom.id(externalFltTgtId).appendChild(divCont);
tf.externalFltEls.push(divCont);
} else {
container.appendChild(divCont);
}
this.checkListDiv[colIndex] = divCont;
tf.fltIds.push(tf.prfxFlt + colIndex + '_' + tf.id);
if (!tf.loadFltOnDemand) {
this.build(colIndex);
} else {
Event.add(divCont, 'click', (evt) => this.onCheckListClick(evt));
divCont.appendChild(Dom.text(this.activateCheckListTxt));
}
this.emitter.on(
['build-checklist-filter'],
(tf, colIndex, isExternal) => this.build(colIndex, isExternal)
);
this.emitter.on(
['select-checklist-options'],
(tf, colIndex, values) => this.selectOptions(colIndex, values)
);
this.initialized = true;
}
/**
* Build checklist UI
* @param {Number} colIndex Column index
*/
build(colIndex) {
let tf = this.tf;
colIndex = parseInt(colIndex, 10);
this.emitter.emit('before-populating-filter', tf, colIndex);
this.opts = [];
this.optsTxt = [];
let flt = this.checkListDiv[colIndex];
let ul = Dom.create(
'ul', ['id', tf.fltIds[colIndex]], ['colIndex', colIndex]);
ul.className = this.checkListCssClass;
Event.add(ul, 'change', (evt) => this.onChange(evt));
let rows = tf.tbl.rows;
this.isCustom = tf.isCustomOptions(colIndex);
let activeIdx;
let activeFilterId = tf.getActiveFilterId();
if (tf.linkedFilters && activeFilterId) {
activeIdx = tf.getColumnIndexFromFilterId(activeFilterId);
}
let filteredDataCol = [];
if (tf.linkedFilters && tf.disableExcludedOptions) {
this.excludedOpts = [];
}
flt.innerHTML = '';
for (let k = tf.refRow; k < tf.nbRows; k++) {
// always visible rows don't need to appear on selects as always
// valid
if (tf.hasVisibleRows && tf.visibleRows.indexOf(k) !== -1) {
continue;
}
let cells = rows[k].cells;
let ncells = cells.length;
// checks if row has exact cell #
if (ncells !== tf.nbCells || this.isCustom) {
continue;
}
// this loop retrieves cell data
for (let j = 0; j < ncells; j++) {
// WTF: cyclomatic complexity hell :)
if ((colIndex === j && (!tf.linkedFilters ||
(tf.linkedFilters && tf.disableExcludedOptions))) ||
(colIndex === j && tf.linkedFilters &&
((rows[k].style.display === '' && !tf.paging) ||
(tf.paging && ((!activeIdx ||
activeIdx === colIndex) ||
(activeIdx != colIndex &&
tf.validRowsIndex.indexOf(k) != -1)))))) {
let cellData = tf.getCellData(cells[j]);
//Vary Peter's patch
let cellString = Str.matchCase(cellData, tf.matchCase);
// checks if celldata is already in array
if (!Arr.has(this.opts, cellString, tf.matchCase)) {
this.opts.push(cellData);
}
let filteredCol = filteredDataCol[j];
if (tf.linkedFilters && tf.disableExcludedOptions) {
if (!filteredCol) {
filteredCol = tf.getFilteredDataCol(j);
}
if (!Arr.has(filteredCol, cellString, tf.matchCase) &&
!Arr.has(this.excludedOpts,
cellString, tf.matchCase)) {
this.excludedOpts.push(cellData);
}
}
}
}
}
//Retrieves custom values
if (this.isCustom) {
let customValues = tf.getCustomOptions(colIndex);
this.opts = customValues[0];
this.optsTxt = customValues[1];
}
if (tf.sortSlc && !this.isCustom) {
if (!tf.matchCase) {
this.opts.sort(Sort.ignoreCase);
if (this.excludedOpts) {
this.excludedOpts.sort(Sort.ignoreCase);
}
} else {
this.opts.sort();
if (this.excludedOpts) {
this.excludedOpts.sort();
}
}
}
//asc sort
if (tf.sortNumAsc.indexOf(colIndex) != -1) {
try {
this.opts.sort(Sort.numSortAsc);
if (this.excludedOpts) {
this.excludedOpts.sort(Sort.numSortAsc);
}
if (this.isCustom) {
this.optsTxt.sort(Sort.numSortAsc);
}
} catch (e) {
throw new Error(SORT_ERROR.replace('{0}', colIndex)
.replace('{1}', 'ascending'));
}//in case there are alphanumeric values
}
//desc sort
if (tf.sortNumDesc.indexOf(colIndex) != -1) {
try {
this.opts.sort(Sort.numSortDesc);
if (this.excludedOpts) {
this.excludedOpts.sort(Sort.numSortDesc);
}
if (this.isCustom) {
this.optsTxt.sort(Sort.numSortDesc);
}
} catch (e) {
throw new Error(SORT_ERROR.replace('{0}', colIndex)
.replace('{1}', 'descending'));
}//in case there are alphanumeric values
}
this.addChecks(colIndex, ul);
if (tf.loadFltOnDemand) {
flt.innerHTML = '';
}
flt.appendChild(ul);
flt.setAttribute('filled', '1');
this.emitter.emit('after-populating-filter', tf, colIndex, flt);
}
/**
* Add checklist options
* @param {Number} colIndex Column index
* @param {Object} ul Ul element
*/
addChecks(colIndex, ul) {
let tf = this.tf;
let chkCt = this.addTChecks(colIndex, ul);
for (let y = 0; y < this.opts.length; y++) {
let val = this.opts[y]; //item value
let lbl = this.isCustom ? this.optsTxt[y] : val; //item text
let li = Dom.createCheckItem(
tf.fltIds[colIndex] + '_' + (y + chkCt), val, lbl);
li.className = this.checkListItemCssClass;
if (tf.linkedFilters && tf.disableExcludedOptions &&
Arr.has(this.excludedOpts,
Str.matchCase(val, tf.matchCase), tf.matchCase)) {
Dom.addClass(li, this.checkListItemDisabledCssClass);
li.check.disabled = true;
li.disabled = true;
} else {
Event.add(li.check, 'click', (evt) => this.optionClick(evt));
}
ul.appendChild(li);
if (val === '') {
//item is hidden
li.style.display = NONE;
}
}
}
/**
* Add checklist header option
* @param {Number} colIndex Column index
* @param {Object} ul Ul element
*/
addTChecks(colIndex, ul) {
let tf = this.tf;
let chkCt = 1;
let li0 = Dom.createCheckItem(
tf.fltIds[colIndex] + '_0', '', tf.displayAllText);
li0.className = this.checkListItemCssClass;
ul.appendChild(li0);
Event.add(li0.check, 'click', (evt) => this.optionClick(evt));
if (!this.enableCheckListResetFilter) {
li0.style.display = NONE;
}
if (tf.enableEmptyOption) {
let li1 = Dom.createCheckItem(
tf.fltIds[colIndex] + '_1', tf.emOperator, tf.emptyText);
li1.className = this.checkListItemCssClass;
ul.appendChild(li1);
Event.add(li1.check, 'click', (evt) => this.optionClick(evt));
chkCt++;
}
if (tf.enableNonEmptyOption) {
let li2 = Dom.createCheckItem(
tf.fltIds[colIndex] + '_2',
tf.nmOperator,
tf.nonEmptyText
);
li2.className = this.checkListItemCssClass;
ul.appendChild(li2);
Event.add(li2.check, 'click', (evt) => this.optionClick(evt));
chkCt++;
}
return chkCt;
}
/**
* Store checked options in DOM element attribute
* @param {Object} o checklist option DOM element
*/
setCheckListValues(o) {
if (!o) {
return;
}
let tf = this.tf;
let chkValue = o.value; //checked item value
// TODO: provide helper to extract column index, ugly!
let chkIndex = parseInt(o.id.split('_')[2], 10);
let colIdx = tf.getColumnIndexFromFilterId(o.id);
let itemTag = 'LI';
let n = tf.getFilterElement(parseInt(colIdx, 10));
let li = n.childNodes[chkIndex];
let colIndex = n.getAttribute('colIndex');
let fltValue = n.getAttribute('value'); //filter value (ul tag)
let fltIndexes = n.getAttribute('indexes'); //selected items (ul tag)
if (o.checked) {
//show all item
if (chkValue === '') {
if ((fltIndexes && fltIndexes !== '')) {
//items indexes
let indSplit = fltIndexes.split(tf.separator);
//checked items loop
for (let u = 0; u < indSplit.length; u++) {
//checked item
let cChk = Dom.id(tf.fltIds[colIndex] + '_' +
indSplit[u]);
if (cChk) {
cChk.checked = false;
Dom.removeClass(
n.childNodes[indSplit[u]],
this.checkListSlcItemCssClass
);
}
}
}
n.setAttribute('value', '');
n.setAttribute('indexes', '');
} else {
fltValue = (fltValue) ? fltValue : '';
chkValue = Str.trim(fltValue + ' ' + chkValue + ' ' +
tf.orOperator);
chkIndex = fltIndexes + chkIndex + tf.separator;
n.setAttribute('value', chkValue);
n.setAttribute('indexes', chkIndex);
//1st option unchecked
if (Dom.id(tf.fltIds[colIndex] + '_0')) {
Dom.id(tf.fltIds[colIndex] + '_0').checked = false;
}
}
if (li.nodeName === itemTag) {
Dom.removeClass(
n.childNodes[0], this.checkListSlcItemCssClass);
Dom.addClass(li, this.checkListSlcItemCssClass);
}
} else { //removes values and indexes
if (chkValue !== '') {
let replaceValue = new RegExp(
Str.rgxEsc(chkValue + ' ' + tf.orOperator));
fltValue = fltValue.replace(replaceValue, '');
n.setAttribute('value', Str.trim(fltValue));
let replaceIndex = new RegExp(
Str.rgxEsc(chkIndex + tf.separator));
fltIndexes = fltIndexes.replace(replaceIndex, '');
n.setAttribute('indexes', fltIndexes);
}
if (li.nodeName === itemTag) {
Dom.removeClass(li, this.checkListSlcItemCssClass);
}
}
}
/**
* Select filter options programmatically
* @param {Number} colIndex Column index
* @param {Array} values Array of option values to select
*/
selectOptions(colIndex, values = []) {
let tf = this.tf;
let flt = tf.getFilterElement(colIndex);
if (tf.getFilterType(colIndex) !== CHECKLIST || !flt) {
return;
}
let lisNb = Dom.tag(flt, 'li').length;
flt.setAttribute('value', '');
flt.setAttribute('indexes', '');
for (let k = 0; k < lisNb; k++) {
let li = Dom.tag(flt, 'li')[k],
lbl = Dom.tag(li, 'label')[0],
chk = Dom.tag(li, 'input')[0],
lblTxt = Str.matchCase(Dom.getText(lbl), tf.caseSensitive);
if (lblTxt !== '' && Arr.has(values, lblTxt, tf.caseSensitive)) {
chk.checked = true;
this.setCheckListValues(chk);
}
else {
chk.checked = false;
this.setCheckListValues(chk);
}
}
}
/**
* Get filter values for a given column index
* @param {Number} colIndex Column index
* @returns {Array} values Collection of selected values
*/
getValues(colIndex) {
let tf = this.tf;
let flt = tf.getFilterElement(colIndex);
let fltAttr = flt.getAttribute('value');
let values = Types.isEmpty(fltAttr) ? '' : fltAttr;
//removes last operator ||
values = values.substr(0, values.length - 3);
//turn || separated values into array
values = values.split(' ' + tf.orOperator + ' ');
return values;
}
destroy() {
this.emitter.off(
['build-checklist-filter'],
(tf, colIndex, isExternal) => this.build(colIndex, isExternal)
);
this.emitter.off(
['select-checklist-options'],
(tf, colIndex, values) => this.selectOptions(colIndex, values)
);
}
}