UNPKG

@browser.style/data-grid

Version:

Dynamic data grid component with sorting, filtering, and pagination support

215 lines (184 loc) 7.5 kB
import { renderTBody, renderTHead } from './render.table.js'; import { handleSorting } from './data.js'; import handleKeyboardEvents from './events.keyboard.js'; import { addEventListeners, getKeyValueObject, getObj } from './utility.js'; export function attachCustomEventHandlers(context) { // Append new rows to the table context.addEventListener('dg:append', (event) => { const { detail } = event; context.state.tbody.push(...detail); context.state.rows = context.state.tbody.length; context.state.pages = Math.floor(context.state.rows / context.state.itemsPerPage); renderTBody(context); }); // Clear selected rows context.addEventListener('dg:clearselected', () => { context.state.selected.clear(); context.form.elements.selected.value = 0; renderTBody(context); if (context.toggle) { context.toggle.checked = false; context.toggle.indeterminate = false; } context.dispatch('dg:selected', { detail: [] }); }); // Event listener for retrieving selected rows based on composite keys context.addEventListener('dg:getselected', () => { const selected = [...context.state.selected].map(key => { const tempNode = { parentNode: { dataset: { keys: key } } }; return getObj(context.state, tempNode); }); context.dispatch('dg:selected', { detail: selected.filter(item => item !== null) }); }); } export function attachEventListeners(context) { const { form, table } = context; // Pagination controls form.elements.stepdown.addEventListener('click', () => context.navigatePage(null, 'prev')); form.elements.stepup.addEventListener('click', () => context.navigatePage(null, 'next')); form.elements.first.addEventListener('click', () => context.navigatePage(0)); form.elements.last.addEventListener('click', () => context.navigatePage(context.state.pages - 1)); // Printable option form.elements.preview.addEventListener('click', () => context.print()); form.elements.print.addEventListener('click', () => context.print(true)); form.elements.printoptions.addEventListener('change', (event) => { context.state.printOptions = event.target.value; }); // Column filter form.elements.columnfilter.addEventListener('change', (event) => { const column = context.state.thead.find(col => col.field === event.target.name); if (column) { column.hidden = !event.target.checked; context.colgroup.innerHTML = ''; renderTHead(context); renderTBody(context); } }); // Select / Deselect All form.elements.selectall.addEventListener('click', () => { const selectAll = form.elements.selectall.value === 'true' ? false : true; form.elements.selectall.value = selectAll; context.selectRows(table.tBodies[0].rows, selectAll, true, true); }); // Searchable option addEventListeners(form.elements.searchterm, ['input', 'search'], e => context.setAttribute('searchterm', e.target.value)); form.elements.searchmethod.addEventListener('change', e => { context.setAttribute('searchmethod', e.target.value); renderTBody(context); }); // Layout and textwrap options form.elements.layoutfixed.addEventListener('change', e => context.table.classList.toggle('--fixed', e.target.checked)); form.elements.textwrap.addEventListener('change', e => context.table.classList.toggle('--no-wrap', !e.target.checked)); // Table click and keyboard events table.addEventListener('click', (event) => handleTableClick(event, context)); table.addEventListener('focus', (event) => handleCellFocus(event), true); table.addEventListener('focusout', (event) => handleCellUpdate(event, context)); table.addEventListener('input', (event) => handleCellEdit(event, context)); table.addEventListener('keydown', (event) => handleKeyboardEvents(event, context)); form.addEventListener('input', (event) => handleFormInput(event, context)); // Density options if (form.elements.density) { form.elements.density.addEventListener('change', (event) => { Object.values(context.settings.densityOptions).forEach(option => { table.classList.remove(option.class); }); const densityValue = event.target.value || form.elements.density.value; const selected = context.settings.densityOptions[densityValue]; if (selected) table.classList.add(selected.class); }); const checked = form.elements.density.querySelector('input:checked'); if (checked) { const selected = context.settings.densityOptions[checked.value]; if (selected) table.classList.add(selected.class); } } } function handleFormInput(event, context) { const input = event.target; if (input.name === 'itemsperpage') context.setAttribute('itemsperpage', parseInt(input.value, 10)); if (input.name === 'page') context.setAttribute('page', parseInt(input.value, 10) - 1); } function handleTableClick(event, context) { const { table, state, settings } = context; const node = event.target; if (node === table) return; if (['TD', 'TH'].includes(node.nodeName)) { state.cellIndex = node.cellIndex; state.rowIndex = node.parentNode.rowIndex; // Sorting logic if (state.rowIndex === 0 && node.nodeName === 'TH') { const index = node.dataset.sortIndex; handleSorting(context, index); } // Handle cell-specific events const columnConfig = state.thead[settings.selectable && state.cellIndex > 0 ? state.cellIndex - 1 : state.cellIndex]; if (node.nodeName === 'TD' && columnConfig?.event) { const { row, rowIndex } = getObj(state, node) || {}; const keyValueObject = getKeyValueObject(state, node); if (row && rowIndex !== undefined) { const eventData = { keys: keyValueObject, row, rowIndex, ...columnConfig.eventData }; context.dispatch(columnConfig.event, eventData); } } context.setActive(); } if (settings.selectable && node.nodeName === 'INPUT') { if (node.hasAttribute('data-toggle-row')) { context.selectRows([node.closest('tr')], true); } else if (node.hasAttribute('data-toggle-all')) { const allRows = table.tBodies[0].rows; context.selectRows(allRows, node.checked, true, event.shiftKey); } } } function handleCellFocus(event) { const cell = event.target; if (cell.isContentEditable && !cell.dataset.oldValue) { const trimmedContent = cell.textContent.trim(); cell.dataset.oldValue = trimmedContent; cell.dataset.newValue = trimmedContent; } } /** * Handles the cell edit event. * * This function is triggered when a cell in the data grid is edited. * If the cell is content editable, it stores the trimmed text content * of the cell in the `data-new-value` attribute. * * @param {Event} event - The event object representing the cell edit event. */ function handleCellEdit(event) { const cell = event.target; if (cell.isContentEditable) { cell.dataset.newValue = cell.textContent.trim(); } } function handleCellUpdate(event, context) { const cell = event.target; if (!cell.isContentEditable) return; const { newValue, oldValue } = cell.dataset; if (newValue !== oldValue) { const adjustedCellIndex = context.settings.selectable && cell.cellIndex > 0 ? cell.cellIndex - 1 : cell.cellIndex; const columnConfig = context.state.thead[adjustedCellIndex]; const field = columnConfig.field; const { row, rowIndex } = getObj(context.state, cell) || {}; row[field] = newValue; context.dispatchEvent(new CustomEvent('dg:cellchange', { detail: { field, newValue, oldValue, row, rowIndex } })); cell.dataset.oldValue = newValue; } cell.dataset.newValue = cell.textContent.trim(); }