templates/js/library/data_table/custom_data_table.js line 1

Open in your IDE?
  1. class CustomDataTable {
  2.     options = {};
  3.     selector = null;
  4.     table = null;
  5.     eventDispatcher = null;
  6.     static EVENTS = {
  7.         ON_TABLE_FILTERED: 'tableFiltered'
  8.     };
  9.     constructor(selector, options = {}) {
  10.         this.selector = selector;
  11.         this.options = {
  12.             dom: 'i lfrtip',   // i = info сверху
  13.             "lengthMenu": [[10, 20, 50, 100, 200, 300, 500, 700, -1], [10, 20, 50, 100, 200, 300, 500, 700, "Все"]],
  14.             language: {
  15.                 url: '/js/vendor/datatables/ru.json'
  16.             },
  17.             drawRowNumbersForColIndex: undefined,
  18.             ...options
  19.         };
  20.         this.eventDispatcher = new EventDispatcher();
  21.     }
  22.     static getOptionsExample() {
  23.         return {
  24.             "lengthMenu": [[10, 20, 50, 100, 200, 300, 500, 700, -1], [10, 20, 50, 100, 200, 300, 500, 700, "Все"]],
  25.             "columnDefs": [ // Columns definitions
  26.                 {
  27.                     "targets": 0,
  28.                     "type": "num",
  29.                     "visible": false,
  30.                 },
  31.                 {
  32.                     "targets": 1,
  33.                     "type": "date-d-m-Y"
  34.                 },
  35.             ],
  36.             drawRowNumbersForColIndex: 0,
  37.             "dropdownFiltersConfig": {
  38.                 "elementsData": [
  39.                     {
  40.                         "columnIndex": 2, //3th column
  41.                         "dropdownId": "departmentDropdown", // Dropdown button ID
  42.                         "badgeId": "departmentBadge", // Badge ID to show filter status (optional)
  43.                         "textForAll": "All" // Text to show when 'all' is selected (optional)
  44.                     },
  45.                 ]
  46.             },
  47.         }
  48.     }
  49.     getAllFilteredTableData() {
  50.         let allData = [];
  51.         this.table.rows({ search: 'applied' }).every((rowIdx) => {
  52.             let row = this.table.row(rowIdx);
  53.             allData.push(row.data());
  54.         });
  55.         return allData;
  56.     }
  57.     downloadCsv(filename = 'data_table_export.csv') {
  58.         const delimiter = ';';
  59.         const rows = [];
  60.         // collect headers and escape
  61.         const headers = [];
  62.         this.table.columns().every((index) => {
  63.             const raw = this.table.column(index).header().innerText;
  64.             const safe = String(raw).replace(/"/g, '""');
  65.             headers.push(`"${safe}"`);
  66.         });
  67.         rows.push(headers.join(delimiter));
  68.         // collect rows (respect search: 'applied')
  69.         let number = 0;
  70.         this.table.rows({ search: 'applied' }).every((rowIdx) => {
  71.             const row = this.table.row(rowIdx);
  72.             const rowData = [];
  73.             row.columns().every((colIdx) => {
  74.                 let cell = row.data()[colIdx];
  75.                 if (this.options.drawRowNumbersForColIndex === colIdx) {
  76.                     cell = ++number;
  77.                 }
  78.                 const raw = cell === null || cell === undefined ? '' : String(cell);
  79.                 const safe = raw.replace(/"/g, '""');
  80.                 rowData.push(`"${safe}"`);
  81.             });
  82.             rows.push(rowData.join(delimiter));
  83.         });
  84.         // Join with CRLF for Windows compatibility and prefix with UTF-8 BOM
  85.         const csvString = '\uFEFF' + rows.join('\r\n');
  86.         const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
  87.         const link = document.createElement('a');
  88.         if (link.download !== undefined) {
  89.             const url = URL.createObjectURL(blob);
  90.             link.setAttribute('href', url);
  91.             link.setAttribute('download', filename);
  92.             link.style.visibility = 'hidden';
  93.             document.body.appendChild(link);
  94.             link.click();
  95.             document.body.removeChild(link);
  96.         }
  97.     }
  98.     initDataTable() {
  99.         let columnDefs = [];
  100.         let dropdownFiltersConfig = {
  101.             elementsData: []
  102.         };
  103.         $(`${this.selector} th`).each(function(index) {
  104.             let hideCol = $(this).is('[data-table-hide-col]');
  105.             let tableType = $(this).data('table-type');
  106.             let optionsItem = {
  107.                 "targets": index,
  108.             };
  109.             if (hideCol) {
  110.                 optionsItem['visible'] = false;
  111.             }
  112.             if (tableType) {
  113.                 optionsItem['type'] = tableType;
  114.             }
  115.             if (Object.keys(optionsItem).length > 1) {
  116.                 columnDefs.push(optionsItem);
  117.             }
  118.             let dropdownFilterConfig_elementsDataItem = {};
  119.             let dropdownId = $(this).data('table-dropdown-id');
  120.             if (dropdownId) {
  121.                 dropdownFilterConfig_elementsDataItem['columnIndex'] = index;
  122.                 dropdownFilterConfig_elementsDataItem['dropdownId'] = dropdownId;
  123.                 let badgeId = $(this).data('table-badge-id');
  124.                 if (badgeId) {
  125.                     dropdownFilterConfig_elementsDataItem['badgeId'] = badgeId;
  126.                 }
  127.                 let textForAll = $(this).data('table-dropdown-text-for-all');
  128.                 if (textForAll) {
  129.                     dropdownFilterConfig_elementsDataItem['textForAll'] = textForAll;
  130.                 }
  131.                 dropdownFiltersConfig.elementsData.push(dropdownFilterConfig_elementsDataItem);
  132.             }
  133.         });
  134.         if (!this.options.columnDefs || this.options.columnDefs.length === 0) {
  135.             console.log(columnDefs)
  136.             this.options.columnDefs = columnDefs;
  137.         }
  138.         if (dropdownFiltersConfig && (!this.options.dropdownFiltersConfig
  139.             || !this.options.dropdownFiltersConfig.elementsData
  140.             || this.options.dropdownFiltersConfig.elementsData.length === 0)) {
  141.             this.options.dropdownFiltersConfig = dropdownFiltersConfig;
  142.         }
  143.         this.table = $(this.selector).DataTable(this.options);
  144.         $(document).ready(() => {
  145.             this.populateDropdowns();
  146.             this.table.on('draw', () => {
  147.                 this.populateDropdowns();
  148.             });
  149.         });
  150.         this.eventDispatcher.addEventListener(CustomDataTable.EVENTS.ON_TABLE_FILTERED, () => {
  151.             if (this.options.drawRowNumbersForColIndex !== null && this.options.drawRowNumbersForColIndex !== undefined) {
  152.                 this.drawRowNumbers();
  153.             }
  154.         });
  155.         return this.table;
  156.     }
  157.     drawRowNumbers() {
  158.         this.table.column(this.options.drawRowNumbersForColIndex, { search: 'applied' }).nodes().each(function(cell, i) {
  159.             cell.innerHTML = i + 1;
  160.         });
  161.     }
  162.     populateDropdowns() {
  163.         if (this.options.dropdownFiltersConfig && this.options.dropdownFiltersConfig.elementsData) {
  164.             this.options.dropdownFiltersConfig.elementsData.forEach((options) => {
  165.                 this.populateDropdown(options);
  166.             });
  167.         }
  168.     }
  169.     populateDropdown(options) {
  170.         const me = this;
  171.         options = {
  172.             columnIndex: null,
  173.             dropdownId: null,
  174.             badgeId: null,
  175.             textForAll: 'Все',
  176.             ...options
  177.         };
  178.         var dropdownButton = $("#" + options.dropdownId);
  179.         var dropdownMenu = dropdownButton.next('.dropdown-menu');
  180.         // Read multiple flag from data attribute on button (template sets data-multiple)
  181.         var isMultiple = false;
  182.         try {
  183.             var dataMultiple = dropdownButton.data('multiple');
  184.             if (typeof dataMultiple === 'boolean') {
  185.                 isMultiple = dataMultiple;
  186.             } else if (typeof dataMultiple === 'string') {
  187.                 isMultiple = dataMultiple === 'true';
  188.             }
  189.         } catch (e) {
  190.             isMultiple = false;
  191.         }
  192.         // Get unique values from column
  193.         var uniqueValues = this.table
  194.             .column(options.columnIndex)
  195.             .data()
  196.             .unique()
  197.             .sort()
  198.             .toArray();
  199.         // Remove previously generated non-all items
  200.         dropdownMenu.find('.dropdown-item:not([data-filter="all"])').remove();
  201.         // Append new items (галочка вместо checkbox)
  202.         uniqueValues.forEach(function(value) {
  203.             var item = $('<a class="dropdown-item d-flex align-items-center" href="#" role="menuitem" aria-pressed="false"></a>');
  204.             item.attr('data-filter', value);
  205.             // Общий контейнер с галочкой (скрыт до выбора)
  206.             var checkWrap = $('<span class="filter-check me-2" style="visibility:hidden;"></span>');
  207.             var checkIcon = $('<i class="fas fa-check text-white"></i>');
  208.             checkWrap.append(checkIcon);
  209.             var textSpan = $('<span class="filter-text"></span>').text(value);
  210.             item.append(checkWrap).append(textSpan);
  211.             dropdownMenu.append(item);
  212.         });
  213.         // Ensure no duplicate handlers
  214.         dropdownMenu.find('.dropdown-item').off('click');
  215.         var selectedValues = dropdownButton.data('selectedValues') || [];
  216.         function updateCheckMark($el, selected) {
  217.             var wrap = $el.find('.filter-check');
  218.             if (!wrap.length) return;
  219.             if (selected) {
  220.                 wrap.css('visibility', 'visible');
  221.                 $el.addClass('active').attr('aria-pressed', 'true');
  222.             } else {
  223.                 wrap.css('visibility', 'hidden');
  224.                 $el.removeClass('active').attr('aria-pressed', 'false');
  225.             }
  226.         }
  227.         // Initialize existing selections
  228.         if (selectedValues && selectedValues.length > 0) {
  229.             dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function() {
  230.                 var $it = $(this);
  231.                 var val = $it.data('filter');
  232.                 updateCheckMark($it, selectedValues.indexOf(val) !== -1);
  233.             });
  234.             if (selectedValues.length === 1) {
  235.                 dropdownButton.text(selectedValues[0]);
  236.                 if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues[0]);
  237.             } else {
  238.                 dropdownButton.text(selectedValues.length + ' выбрано');
  239.                 if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues.length);
  240.             }
  241.         } else {
  242.             dropdownButton.text(options.textForAll || 'Все');
  243.             if (options.badgeId) $(options.badgeId).addClass('d-none');
  244.         }
  245.         dropdownMenu.find('.dropdown-item').on('click', function(e) {
  246.             e.preventDefault();
  247.             var $el = $(this);
  248.             var filterValue = $el.data('filter');
  249.             if (!isMultiple) {
  250.                 if (filterValue === 'all') {
  251.                     dropdownButton.data('selectedValues', []);
  252.                     dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function(){ updateCheckMark($(this), false); });
  253.                     dropdownButton.text(options.textForAll || 'Все');
  254.                     if (options.badgeId) $(options.badgeId).addClass('d-none');
  255.                     me.table.column(options.columnIndex).search('').draw();
  256.                 } else {
  257.                     dropdownButton.data('selectedValues', [filterValue]);
  258.                     dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function(){
  259.                         var $other = $(this);
  260.                         updateCheckMark($other, $other.data('filter') === filterValue);
  261.                     });
  262.                     dropdownButton.text(filterValue);
  263.                     if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + filterValue);
  264.                     let esc = StringUtils.escapeRegex(filterValue);
  265.                     me.table.column(options.columnIndex).search('^' + esc + '$', true, false).draw();
  266.                 }
  267.             } else {
  268.                 if (filterValue === 'all') {
  269.                     selectedValues = [];
  270.                     dropdownButton.data('selectedValues', selectedValues);
  271.                     dropdownMenu.find('.dropdown-item:not([data-filter="all"])').each(function(){ updateCheckMark($(this), false); });
  272.                     dropdownButton.text(options.textForAll || 'Все');
  273.                     if (options.badgeId) $(options.badgeId).addClass('d-none');
  274.                     me.table.column(options.columnIndex).search('').draw();
  275.                 } else {
  276.                     var idx = selectedValues.indexOf(filterValue);
  277.                     if (idx === -1) {
  278.                         selectedValues.push(filterValue);
  279.                         updateCheckMark($el, true);
  280.                     } else {
  281.                         selectedValues.splice(idx, 1);
  282.                         updateCheckMark($el, false);
  283.                     }
  284.                     if (selectedValues.length === 0) {
  285.                         dropdownButton.text(options.textForAll || 'Все');
  286.                         if (options.badgeId) $(options.badgeId).addClass('d-none');
  287.                         dropdownButton.data('selectedValues', selectedValues);
  288.                         me.table.column(options.columnIndex).search('').draw();
  289.                     } else {
  290.                         if (selectedValues.length === 1) {
  291.                             dropdownButton.text(selectedValues[0]);
  292.                             if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues[0]);
  293.                         } else {
  294.                             dropdownButton.text(selectedValues.length + ' выбрано');
  295.                             if (options.badgeId) $(options.badgeId).removeClass('d-none').text('Filter: ' + selectedValues.length);
  296.                         }
  297.                         var escaped = selectedValues.map(function(v){ return StringUtils.escapeRegex(v); });
  298.                         var regex = '^(' + escaped.join('|') + ')$';
  299.                         dropdownButton.data('selectedValues', selectedValues);
  300.                         me.table.column(options.columnIndex).search(regex, true, false).draw();
  301.                     }
  302.                 }
  303.             }
  304.             me.eventDispatcher.emit(CustomDataTable.EVENTS.ON_TABLE_FILTERED);
  305.         });
  306.     }
  307. }