UNPKG

@browser.style/data-grid

Version:

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

177 lines (154 loc) 6.13 kB
import { calculatePages, camelCase, capitalize, consoleLog } from './utility.js'; export function dataFromTable(table, itemsPerPage = 5, selectable = 0) { try { const { thead, hiddenCount } = getTableHead(table); const tbody = getTableBody(table, thead); return { cols: (thead.length - hiddenCount) + (selectable ? 1 : 0), items: tbody.length, pages: calculatePages(tbody.length, itemsPerPage), tbody, thead }; } catch (error) { consoleLog(`Error extracting data from table: ${error}`, '#F00'); return {}; } } function generateTheadFromTbody(tbody) { return Object.keys(tbody[0] || {}).map(key => ({ field: key, label: capitalize(key), hidden: false })); } function getTableBody(table, thead) { return Array.from(table.tBodies[0].rows, row => { return Array.from(row.cells).reduce((rowData, cell, index) => { const field = thead[index].field; rowData[field] = cell.textContent; return rowData; }, {}); }); } function getTableHead(table) { const thead = []; let hiddenCount = 0; for (const cell of table.tHead.rows[0].cells) { const isHidden = cell.hasAttribute('hidden'); if (isHidden) hiddenCount++; thead.push({ field: camelCase(cell.textContent), hidden: isHidden, label: cell.textContent, uid: cell.hasAttribute('data-uid') }); } return { thead, hiddenCount }; } export function handleSorting(context, index) { const currentSortIndex = parseInt(context.getAttribute('sortindex'), 10); const currentSortOrder = parseInt(context.getAttribute('sortorder'), 10); if (index !== undefined) { if (currentSortIndex === parseInt(index, 10)) { context.setAttribute('sortorder', currentSortOrder === 0 ? 1 : 0); if (currentSortOrder === 1) context.removeAttribute('sortindex'); } else { context.setAttribute('sortindex', parseInt(index, 10)); context.setAttribute('sortorder', 0); } } } /** * Reorders columns based on displayOrder setting */ function reorderColumns(thead, tbody, displayOrder) { if (!displayOrder?.length) return { thead, tbody }; // Create map of field positions in displayOrder const orderMap = displayOrder.reduce((acc, field, idx) => { acc[field] = idx; return acc; }, {}); // Reorder thead const reorderedThead = [...thead].sort((a, b) => { const aOrder = orderMap[a.field] ?? Number.MAX_SAFE_INTEGER; const bOrder = orderMap[b.field] ?? Number.MAX_SAFE_INTEGER; return aOrder - bOrder; }); // Create field mapping for tbody reordering const fieldMapping = thead.map(col => col.field); const newFieldMapping = reorderedThead.map(col => col.field); // Reorder tbody by creating new objects with reordered properties const reorderedTbody = tbody.map(row => { const newRow = {}; newFieldMapping.forEach((field, idx) => { newRow[field] = row[fieldMapping[fieldMapping.indexOf(field)]]; }); return newRow; }); return { thead: reorderedThead, tbody: reorderedTbody }; } /** * Parses the provided data and updates the context's table structure. * If thead or tbody are not provided, they will be generated based on the data. * Thead can also be merged with a provided config. * * @param {Object|Array} data - The data to be parsed, which can either be an object containing thead and tbody, or just an array representing tbody. * @param {Object} context - The context object containing state, settings, and config. * @param {Object} context.config - Configuration object that can provide additional thead information. * @param {Array} context.config.thead - Array of column definitions from the config. * @param {Object} context.settings - Settings object, including table settings like 'selectable'. * @param {boolean} context.settings.selectable - Whether the table allows row selection. * @param {Object} context.state - State object that contains table information. * @param {number} context.state.itemsPerPage - Number of items displayed per page. * @param {Function} context.log - Function to log messages for debugging. * * @returns {Object} An object containing the parsed table structure with the following properties: * - cols: The number of visible columns (accounting for hidden columns and selectable rows). * - items: The number of items in tbody. * - pages: The total number of pages calculated based on the number of items and items per page. * - tbody: The parsed tbody array (the rows of the table). * - thead: The parsed or generated thead array (the headers of the table). * * @throws Will throw an error if parsing the data fails. */ export function parseData(data, context) { try { let { thead = [], tbody = [] } = data; const externalNavigation = context.settings.externalNavigation; // If no thead is provided but data is an array (assume tbody items) if (!thead.length && tbody.length) { thead = generateTheadFromTbody(tbody); } // If no tbody is provided, assume data itself is tbody if (!tbody.length && Array.isArray(data)) { tbody = data; thead = generateTheadFromTbody(tbody); } // Check for a settings object and merge thead from settings if it exists if (context.settings?.thead) { thead = thead.map((col) => { const configCol = context.settings.thead.find((c) => c.field === col.field); return configCol ? { ...col, ...configCol } : col; }); } // Calculate hidden columns count const hiddenCount = thead.filter(cell => cell.hidden).length; // Apply column reordering if displayOrder is set if (context.settings?.displayOrder) { const { thead: reorderedThead, tbody: reorderedTbody } = reorderColumns(thead, tbody, context.settings.displayOrder); thead = reorderedThead; tbody = reorderedTbody; } return { cols: (thead.length - hiddenCount) + (context.settings.selectable ? 1 : 0), items: externalNavigation ? context.state.items : tbody.length, pages: externalNavigation ? context.state.pages : calculatePages(tbody.length, context.state.itemsPerPage), tbody, thead }; } catch (error) { context.log(`Error parsing data: ${error}`, '#F00', context.settings.debug); throw error; } }