class CustomDataTable {
options = {};
selector = null;
table = null;
eventDispatcher = null;
static EVENTS = {
ON_TABLE_FILTERED: 'tableFiltered'
};
constructor(selector, options = {}) {
this.selector = selector;
this.options = {
dom: 'i lfrtip', // i = info сверху
"lengthMenu": [[10, 20, 50, 100, 200, 300, 500, 700, -1], [10, 20, 50, 100, 200, 300, 500, 700, "Все"]],
language: {
url: '/js/vendor/datatables/ru.json'
},
drawRowNumbersForColIndex: undefined,
...options
};
this.eventDispatcher = new EventDispatcher();
}
static getOptionsExample() {
return {
"lengthMenu": [[10, 20, 50, 100, 200, 300, 500, 700, -1], [10, 20, 50, 100, 200, 300, 500, 700, "Все"]],
"columnDefs": [ // Columns definitions
{
"targets": 0,
"type": "num",
"visible": false,
},
{
"targets": 1,
"type": "date-d-m-Y"
},
],
drawRowNumbersForColIndex: 0,
"dropdownFiltersConfig": {
"elementsData": [
{
"columnIndex": 2, //3th column
"dropdownId": "departmentDropdown", // Dropdown button ID
"badgeId": "departmentBadge", // Badge ID to show filter status (optional)
"textForAll": "All" // Text to show when 'all' is selected (optional)
},
]
},
}
}
getAllFilteredTableData() {
let allData = [];
this.table.rows({ search: 'applied' }).every((rowIdx) => {
let row = this.table.row(rowIdx);
allData.push(row.data());
});
return allData;
}
downloadCsv(filename = 'data_table_export.csv') {
const delimiter = ';';
const rows = [];
// collect headers and escape
const headers = [];
this.table.columns().every((index) => {
const raw = this.table.column(index).header().innerText;
const safe = String(raw).replace(/"/g, '""');
headers.push(`"${safe}"`);
});
rows.push(headers.join(delimiter));
// collect rows (respect search: 'applied')
let number = 0;
this.table.rows({ search: 'applied' }).every((rowIdx) => {
const row = this.table.row(rowIdx);
const rowData = [];
row.columns().every((colIdx) => {
let cell = row.data()[colIdx];
if (this.options.drawRowNumbersForColIndex === colIdx) {
cell = ++number;
}
const raw = cell === null || cell === undefined ? '' : String(cell);
const safe = raw.replace(/"/g, '""');
rowData.push(`"${safe}"`);
});
rows.push(rowData.join(delimiter));
});
// Join with CRLF for Windows compatibility and prefix with UTF-8 BOM
const csvString = '\uFEFF' + rows.join('\r\n');
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
initDataTable() {
let columnDefs = [];
let dropdownFiltersConfig = {
elementsData: []
};
$(`${this.selector} th`).each(function(index) {
let hideCol = $(this).is('[data-table-hide-col]');
let tableType = $(this).data('table-type');
let optionsItem = {
"targets": index,
};
if (hideCol) {
optionsItem['visible'] = false;
}
if (tableType) {
optionsItem['type'] = tableType;
}
if (Object.keys(optionsItem).length > 1) {
columnDefs.push(optionsItem);
}
let dropdownFilterConfig_elementsDataItem = {};
let dropdownId = $(this).data('table-dropdown-id');
if (dropdownId) {
dropdownFilterConfig_elementsDataItem['columnIndex'] = index;
dropdownFilterConfig_elementsDataItem['dropdownId'] = dropdownId;
let badgeId = $(this).data('table-badge-id');
if (badgeId) {
dropdownFilterConfig_elementsDataItem['badgeId'] = badgeId;
}
let textForAll = $(this).data('table-dropdown-text-for-all');
if (textForAll) {
dropdownFilterConfig_elementsDataItem['textForAll'] = textForAll;
}
dropdownFiltersConfig.elementsData.push(dropdownFilterConfig_elementsDataItem);
}
});
if (!this.options.columnDefs || this.options.columnDefs.length === 0) {
console.log(columnDefs)
this.options.columnDefs = columnDefs;
}
if (dropdownFiltersConfig && (!this.options.dropdownFiltersConfig
|| !this.options.dropdownFiltersConfig.elementsData
|| this.options.dropdownFiltersConfig.elementsData.length === 0)) {
this.options.dropdownFiltersConfig = dropdownFiltersConfig;
}
this.table = $(this.selector).DataTable(this.options);
$(document).ready(() => {
this.populateDropdowns();
this.table.on('draw', () => {
this.populateDropdowns();
});
});
this.eventDispatcher.addEventListener(CustomDataTable.EVENTS.ON_TABLE_FILTERED, () => {
if (this.options.drawRowNumbersForColIndex !== null && this.options.drawRowNumbersForColIndex !== undefined) {
this.drawRowNumbers();
}
});
return this.table;
}
drawRowNumbers() {
this.table.column(this.options.drawRowNumbersForColIndex, { search: 'applied' }).nodes().each(function(cell, i) {
cell.innerHTML = i + 1;
});
}
populateDropdowns() {
if (this.options.dropdownFiltersConfig && this.options.dropdownFiltersConfig.elementsData) {
this.options.dropdownFiltersConfig.elementsData.forEach((options) => {
this.populateDropdown(options);
});
}
}
populateDropdown(options) {
const me = this;
options = {
columnIndex: null,
dropdownId: null,
badgeId: null,
textForAll: 'Все',
...options
};
var dropdownButton = $("#" + options.dropdownId);
var dropdownMenu = dropdownButton.next('.dropdown-menu');
// Read multiple flag from data attribute on button (template sets data-multiple)
var isMultiple = false;
try {
var dataMultiple = dropdownButton.data('multiple');
if (typeof dataMultiple === 'boolean') {
isMultiple = dataMultiple;
} else if (typeof dataMultiple === 'string') {
isMultiple = dataMultiple === 'true';
}
} catch (e) {
isMultiple = false;
}
// Get unique values from column
var uniqueValues = this.table
.column(options.columnIndex)
.data()
.unique()
.sort()
.toArray();
// Remove previously generated non-all items
dropdownMenu.find('.dropdown-item:not([data-filter="all"])').remove();
// Append new items (галочка вместо checkbox)
uniqueValues.forEach(function(value) {
var item = $('<a class="dropdown-item d-flex align-items-center" href="#" role="menuitem" aria-pressed="false"></a>');
item.attr('data-filter', value);
// Общий контейнер с галочкой (скрыт до выбора)
var checkWrap = $('<span class="filter-check me-2" style="visibility:hidden;"></span>');
var checkIcon = $('<i class="fas fa-check text-white"></i>');
checkWrap.append(checkIcon);
var textSpan = $('<span class="filter-text"></span>').text(value);
item.append(checkWrap).append(textSpan);
dropdownMenu.append(item);
});
// Ensure no duplicate handlers
dropdownMenu.find('.dropdown-item').off('click');
var selectedValues = dropdownButton.data('selectedValues') || [];
function updateCheckMark($el, selected) {
var wrap = $el.find('.filter-check');
if (!wrap.length) return;
if (selected) {
wrap.css('visibility', 'visible');
$el.addClass('active').attr('aria-pressed', 'true');
} else {
wrap.css('visibility', 'hidden');
$el.removeClass('active').attr('aria-pressed', 'false');
}
}
// Initialize existing selections
if (selectedValues && selectedValues.length > 0) {
dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function() {
var $it = $(this);
var val = $it.data('filter');
updateCheckMark($it, selectedValues.indexOf(val) !== -1);
});
if (selectedValues.length === 1) {
dropdownButton.text(selectedValues[0]);
if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues[0]);
} else {
dropdownButton.text(selectedValues.length + ' выбрано');
if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues.length);
}
} else {
dropdownButton.text(options.textForAll || 'Все');
if (options.badgeId) $(options.badgeId).addClass('d-none');
}
dropdownMenu.find('.dropdown-item').on('click', function(e) {
e.preventDefault();
var $el = $(this);
var filterValue = $el.data('filter');
if (!isMultiple) {
if (filterValue === 'all') {
dropdownButton.data('selectedValues', []);
dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function(){ updateCheckMark($(this), false); });
dropdownButton.text(options.textForAll || 'Все');
if (options.badgeId) $(options.badgeId).addClass('d-none');
me.table.column(options.columnIndex).search('').draw();
} else {
dropdownButton.data('selectedValues', [filterValue]);
dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function(){
var $other = $(this);
updateCheckMark($other, $other.data('filter') === filterValue);
});
dropdownButton.text(filterValue);
if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + filterValue);
let esc = StringUtils.escapeRegex(filterValue);
me.table.column(options.columnIndex).search('^' + esc + '$', true, false).draw();
}
} else {
if (filterValue === 'all') {
selectedValues = [];
dropdownButton.data('selectedValues', selectedValues);
dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function(){ updateCheckMark($(this), false); });
dropdownButton.text(options.textForAll || 'Все');
if (options.badgeId) $(options.badgeId).addClass('d-none');
me.table.column(options.columnIndex).search('').draw();
} else {
var idx = selectedValues.indexOf(filterValue);
if (idx === -1) {
selectedValues.push(filterValue);
updateCheckMark($el, true);
} else {
selectedValues.splice(idx, 1);
updateCheckMark($el, false);
}
if (selectedValues.length === 0) {
dropdownButton.text(options.textForAll || 'Все');
if (options.badgeId) $(options.badgeId).addClass('d-none');
dropdownButton.data('selectedValues', selectedValues);
me.table.column(options.columnIndex).search('').draw();
} else {
if (selectedValues.length === 1) {
dropdownButton.text(selectedValues[0]);
if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues[0]);
} else {
dropdownButton.text(selectedValues.length + ' выбрано');
if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues.length);
}
var escaped = selectedValues.map(function(v){ return StringUtils.escapeRegex(v); });
var regex = '^(' + escaped.join('|') + ')$';
dropdownButton.data('selectedValues', selectedValues);
me.table.column(options.columnIndex).search(regex, true, false).draw();
}
}
}
me.eventDispatcher.emit(CustomDataTable.EVENTS.ON_TABLE_FILTERED);
});
}
}