web-mojo 2.2.69 → 2.2.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.cjs.js.map +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/charts.es.js.map +1 -1
- package/dist/chunks/ChatView-DH42WXgV.js +2 -0
- package/dist/chunks/{ChatView-Dw-iVmht.js.map → ChatView-DH42WXgV.js.map} +1 -1
- package/dist/chunks/ChatView-_8eQTETQ.js +2 -0
- package/dist/chunks/{ChatView-CZ3Key2k.js.map → ChatView-_8eQTETQ.js.map} +1 -1
- package/dist/chunks/Collection-BUv4E9op.js +2 -0
- package/dist/chunks/Collection-BUv4E9op.js.map +1 -0
- package/dist/chunks/Collection-r1ACzUeh.js +2 -0
- package/dist/chunks/Collection-r1ACzUeh.js.map +1 -0
- package/dist/chunks/ContextMenu-BFxliZ03.js +2 -0
- package/dist/chunks/{ContextMenu-8vTiZZQV.js.map → ContextMenu-BFxliZ03.js.map} +1 -1
- package/dist/chunks/ContextMenu-BwJJ4QJE.js +2 -0
- package/dist/chunks/{ContextMenu-DBw0WMTO.js.map → ContextMenu-BwJJ4QJE.js.map} +1 -1
- package/dist/chunks/DataView-DMpNXerv.js +2 -0
- package/dist/chunks/{DataView-DyJKgOn3.js.map → DataView-DMpNXerv.js.map} +1 -1
- package/dist/chunks/DataView-_CACqzRt.js +2 -0
- package/dist/chunks/{DataView-BEovBggn.js.map → DataView-_CACqzRt.js.map} +1 -1
- package/dist/chunks/Dialog-BVCCpLPw.js +3 -0
- package/dist/chunks/Dialog-BVCCpLPw.js.map +1 -0
- package/dist/chunks/Dialog-BYiynSW-.js +3 -0
- package/dist/chunks/Dialog-BYiynSW-.js.map +1 -0
- package/dist/chunks/FormView-Dw7HDwzy.js +3 -0
- package/dist/chunks/{FormView-BRHAIawp.js.map → FormView-Dw7HDwzy.js.map} +1 -1
- package/dist/chunks/FormView-OgrZ7x0z.js +3 -0
- package/dist/chunks/{FormView-B1CXO2t8.js.map → FormView-OgrZ7x0z.js.map} +1 -1
- package/dist/chunks/ListView-2M4I8KHF.js +2 -0
- package/dist/chunks/{ListView-CMZpwyyC.js.map → ListView-2M4I8KHF.js.map} +1 -1
- package/dist/chunks/ListView-B0QbqSPv.js +2 -0
- package/dist/chunks/{ListView-BLFFK_Ir.js.map → ListView-B0QbqSPv.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-DDdDJQFA.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-B0kWK-Js.js.map → MetricsCountryMapView-DDdDJQFA.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-DIlezla0.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-DuBKO7gz.js.map → MetricsCountryMapView-DIlezla0.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-Dt2V0eXP.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-Dg1e6EQJ.js.map → MetricsMiniChartWidget-Dt2V0eXP.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-_N4kzNY_.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-D1w608Jy.js.map → MetricsMiniChartWidget-_N4kzNY_.js.map} +1 -1
- package/dist/chunks/PDFViewer-BruR1RFn.js +2 -0
- package/dist/chunks/{PDFViewer-D_3V8QJe.js.map → PDFViewer-BruR1RFn.js.map} +1 -1
- package/dist/chunks/PDFViewer-CyGFVcvX.js +2 -0
- package/dist/chunks/{PDFViewer-CDeV9OBs.js.map → PDFViewer-CyGFVcvX.js.map} +1 -1
- package/dist/chunks/TableView-CxYpxZvr.js +2 -0
- package/dist/chunks/{TableView-CI_7a-kD.js.map → TableView-CxYpxZvr.js.map} +1 -1
- package/dist/chunks/TableView-DemRVhnX.js +2 -0
- package/dist/chunks/{TableView-CWk5k4LQ.js.map → TableView-DemRVhnX.js.map} +1 -1
- package/dist/chunks/TokenManager-BFaxNsXO.js +2 -0
- package/dist/chunks/{TokenManager-sZgt--C9.js.map → TokenManager-BFaxNsXO.js.map} +1 -1
- package/dist/chunks/TokenManager-IlBEFXqZ.js +2 -0
- package/dist/chunks/{TokenManager-ien2XzwO.js.map → TokenManager-IlBEFXqZ.js.map} +1 -1
- package/dist/chunks/UserProfileView-9vkfCPsp.js +2 -0
- package/dist/chunks/{UserProfileView-kupeq2rN.js.map → UserProfileView-9vkfCPsp.js.map} +1 -1
- package/dist/chunks/UserProfileView-tcBT6XcE.js +2 -0
- package/dist/chunks/{UserProfileView-DnVMHcLH.js.map → UserProfileView-tcBT6XcE.js.map} +1 -1
- package/dist/chunks/WebApp-BFR1zozS.js +2 -0
- package/dist/chunks/{WebApp-CcVF73yg.js.map → WebApp-BFR1zozS.js.map} +1 -1
- package/dist/chunks/WebApp-C82womPC.js +2 -0
- package/dist/chunks/{WebApp-Bti0Gqqo.js.map → WebApp-C82womPC.js.map} +1 -1
- package/dist/chunks/WebSocketClient-Ibi7mLQu.js +2 -0
- package/dist/chunks/{WebSocketClient-Bh0Mmtje.js.map → WebSocketClient-Ibi7mLQu.js.map} +1 -1
- package/dist/chunks/WebSocketClient-QaCUN3EQ.js +2 -0
- package/dist/chunks/{WebSocketClient-CLgYPxWX.js.map → WebSocketClient-QaCUN3EQ.js.map} +1 -1
- package/dist/chunks/index-BaPQHxbL.js +2 -0
- package/dist/chunks/{index-Da9sT-tE.js.map → index-BaPQHxbL.js.map} +1 -1
- package/dist/chunks/index-BdfwxVMZ.js +2 -0
- package/dist/chunks/{index-Aq9ke4vg.js.map → index-BdfwxVMZ.js.map} +1 -1
- package/dist/chunks/{version-XmirKYWA.js → version-C2yYRyPn.js} +2 -2
- package/dist/chunks/{version-XmirKYWA.js.map → version-C2yYRyPn.js.map} +1 -1
- package/dist/chunks/{version-D8JjsPW0.js → version-CaiqhdME.js} +2 -2
- package/dist/chunks/{version-D8JjsPW0.js.map → version-CaiqhdME.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/lightbox.es.js.map +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.cjs.js.map +1 -1
- package/dist/map.es.js +1 -1
- package/dist/map.es.js.map +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.cjs.js.map +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/timeline.es.js.map +1 -1
- package/dist/user-profile.cjs.js +1 -1
- package/dist/user-profile.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +5435 -5435
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +68 -68
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/ChatView-CZ3Key2k.js +0 -2
- package/dist/chunks/ChatView-Dw-iVmht.js +0 -2
- package/dist/chunks/Collection-BWKmydl5.js +0 -2
- package/dist/chunks/Collection-BWKmydl5.js.map +0 -1
- package/dist/chunks/Collection-CmjTsmrP.js +0 -2
- package/dist/chunks/Collection-CmjTsmrP.js.map +0 -1
- package/dist/chunks/ContextMenu-8vTiZZQV.js +0 -2
- package/dist/chunks/ContextMenu-DBw0WMTO.js +0 -2
- package/dist/chunks/DataView-BEovBggn.js +0 -2
- package/dist/chunks/DataView-DyJKgOn3.js +0 -2
- package/dist/chunks/Dialog-Dhqtd9Yz.js +0 -2
- package/dist/chunks/Dialog-Dhqtd9Yz.js.map +0 -1
- package/dist/chunks/Dialog-t_9l2Mou.js +0 -2
- package/dist/chunks/Dialog-t_9l2Mou.js.map +0 -1
- package/dist/chunks/Files-6eRT5k3r.js +0 -2
- package/dist/chunks/Files-6eRT5k3r.js.map +0 -1
- package/dist/chunks/Files-Dh_5PFBn.js +0 -2
- package/dist/chunks/Files-Dh_5PFBn.js.map +0 -1
- package/dist/chunks/FormView-B1CXO2t8.js +0 -3
- package/dist/chunks/FormView-BRHAIawp.js +0 -3
- package/dist/chunks/ListView-BLFFK_Ir.js +0 -2
- package/dist/chunks/ListView-CMZpwyyC.js +0 -2
- package/dist/chunks/MetricsCountryMapView-B0kWK-Js.js +0 -2
- package/dist/chunks/MetricsCountryMapView-DuBKO7gz.js +0 -2
- package/dist/chunks/MetricsMiniChartWidget-D1w608Jy.js +0 -2
- package/dist/chunks/MetricsMiniChartWidget-Dg1e6EQJ.js +0 -2
- package/dist/chunks/PDFViewer-CDeV9OBs.js +0 -2
- package/dist/chunks/PDFViewer-D_3V8QJe.js +0 -2
- package/dist/chunks/Rest-B1eUyLX5.js +0 -2
- package/dist/chunks/Rest-B1eUyLX5.js.map +0 -1
- package/dist/chunks/Rest-BJ3Mvx1L.js +0 -2
- package/dist/chunks/Rest-BJ3Mvx1L.js.map +0 -1
- package/dist/chunks/TableView-CI_7a-kD.js +0 -2
- package/dist/chunks/TableView-CWk5k4LQ.js +0 -2
- package/dist/chunks/ToastService-C2tTooFn.js +0 -3
- package/dist/chunks/ToastService-C2tTooFn.js.map +0 -1
- package/dist/chunks/ToastService-nUaGVpSl.js +0 -3
- package/dist/chunks/ToastService-nUaGVpSl.js.map +0 -1
- package/dist/chunks/TokenManager-ien2XzwO.js +0 -2
- package/dist/chunks/TokenManager-sZgt--C9.js +0 -2
- package/dist/chunks/User-BL9M_PWB.js +0 -2
- package/dist/chunks/User-BL9M_PWB.js.map +0 -1
- package/dist/chunks/User-DqHG5Gr1.js +0 -2
- package/dist/chunks/User-DqHG5Gr1.js.map +0 -1
- package/dist/chunks/UserProfileView-DnVMHcLH.js +0 -2
- package/dist/chunks/UserProfileView-kupeq2rN.js +0 -2
- package/dist/chunks/WebApp-Bti0Gqqo.js +0 -2
- package/dist/chunks/WebApp-CcVF73yg.js +0 -2
- package/dist/chunks/WebSocketClient-Bh0Mmtje.js +0 -2
- package/dist/chunks/WebSocketClient-CLgYPxWX.js +0 -2
- package/dist/chunks/index-Aq9ke4vg.js +0 -2
- package/dist/chunks/index-Da9sT-tE.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TableView-CWk5k4LQ.js","sources":["../../src/core/models/Log.js","../../src/core/models/Member.js","../../src/core/views/table/TableRow.js","../../src/core/utils/DjangoLookups.js","../../src/core/views/table/TableView.js"],"sourcesContent":["\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Log extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/logs',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass LogList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Log,\n endpoint: '/api/logs',\n size: 10,\n ...options,\n });\n }\n}\n\nexport { Log, LogList };\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Member extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group/member',\n });\n }\n\n hasPermission(permission) {\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n return permissions[permission] == true;\n }\n\n async fetchForGroup(groupId) {\n return await this.fetch({ url: `/api/group/${groupId}/member` });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass MemberList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Member,\n endpoint: '/api/group/member',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst MemberForms = {\n\n edit: {\n title: 'Edit Membership',\n fields: [\n {\n name: 'user.display_name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name'\n },\n {\n name: 'metadata.role',\n type: 'text',\n label: 'Role',\n placeholder: 'Enter role'\n },\n {\n name: `is_active`,\n type: 'switch',\n label: \"Is Enabled\",\n columns: 12\n }\n ]\n }\n};\n\n\nMember.EDIT_FORM = MemberForms.edit;\nMember.ADD_FORM = MemberForms.create;\n\n\nexport { Member, MemberList, MemberForms };\n","/**\n * TableRow - Individual row view for TableView\n *\n * Extends ListViewItem to render table rows with proper cell formatting\n * and support for all table features like selection, actions, and context menus.\n *\n * @example\n * const row = new TableRow({\n * model: userModel,\n * columns: tableColumns,\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport ListViewItem from '../list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TableRow extends ListViewItem {\n constructor(options = {}) {\n super({\n tagName: 'tr',\n className: 'table-row',\n enableTooltips: true,\n ...options\n });\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.tableView = options.tableView || options.listView || null;\n\n // Inline editing state\n this.editingCells = new Set(); // Track which cells are being edited\n\n // Override template to generate table cells\n this.template = this.buildRowTemplate();\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Build the row template with table cells\n */\n buildRowTemplate() {\n let template = '';\n\n // Selection checkbox cell\n if (this.tableView && this.tableView.isSelectable()) {\n template += `\n <td style=\"padding: 0;\">\n <div class=\"mojo-select-cell {{#selected}}selected{{/selected}}\"\n data-action=\"select\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </td>\n `;\n }\n\n // Data cells for each column\n this.columns.forEach((column, columnIndex) => {\n const cellClass = column.class || column.className || '';\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n const editableClass = column.editable ? 'editable-cell' : '';\n const combinedClasses = [cellClass, responsiveClasses, editableClass].filter(c => c).join(' ');\n const cellContent = this.buildCellTemplate(column, columnIndex);\n\n // Determine cell action\n let cellAction = column.action;\n if (!cellAction && column.editable) {\n cellAction = 'edit-cell';\n } else if (!cellAction && this.tableView.rowAction) {\n cellAction = this.tableView.rowAction;\n }\n\n if (cellAction) {\n template += `<td class=\"${combinedClasses}\" data-action=\"${cellAction}\" data-column=\"${column.key}\">${cellContent}</td>`;\n } else {\n template += `<td class=\"${combinedClasses}\" data-column=\"${column.key}\">${cellContent}</td>`;\n }\n });\n\n // Actions cell\n if (this.actions) {\n template += this.buildActionsTemplate();\n } else if (this.contextMenu) {\n template += this.buildContextMenuTemplate();\n }\n\n return template;\n }\n\n /**\n * Build template for a single cell\n */\n /**\n * Build template for a single cell\n */\n buildCellTemplate(column, columnIndex = 0) {\n // Build path for Mustache to access the value\n const path = `model.${column.key}`;\n // Support both 'formatter' and 'format' for consistency with DataView\n const formatter = column.formatter || column.format;\n if (formatter) {\n // For string formatters that are pipe expressions\n if (typeof formatter === 'string') {\n return `{{{${path}|${formatter}}}}`;\n } else if (typeof formatter === 'function') {\n // Keep legacy data-formatter key selector for compatibility, but\n // use a per-column id so duplicate keys can be formatted correctly.\n return `<span data-formatter=\"${column.key}\" data-formatter-id=\"${columnIndex}\">{{${path}}}</span>`;\n }\n }\n\n if (column.template) {\n return column.template;\n }\n\n // For editable cells, wrap content in a span for easy replacement\n if (column.editable) {\n return `<span class=\"cell-content\" data-field=\"${column.key}\">{{{${path}}}}</span>`;\n }\n\n return `{{{${path}}}}`;\n }\n\n /**\n * Build actions cell template\n */\n buildActionsTemplate() {\n if (!this.actions || this.actions.length === 0) return '';\n\n const buttons = this.actions.map(action => {\n if (typeof action === 'string') {\n switch (action) {\n case 'view':\n return `\n <button class=\"btn btn-sm btn-outline-primary\"\n data-action=\"view\"\n title=\"View\">\n <i class=\"bi bi-eye\"></i>\n </button>\n `;\n\n case 'edit':\n return `\n <button class=\"btn btn-sm btn-outline-secondary\"\n data-action=\"edit\"\n title=\"Edit\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n `;\n\n case 'delete':\n return `\n <button class=\"btn btn-sm btn-outline-danger\"\n data-action=\"delete\"\n title=\"Delete\">\n <i class=\"bi bi-trash\"></i>\n </button>\n `;\n\n default:\n return '';\n }\n } else if (typeof action === 'object') {\n return `\n <button class=\"btn btn-sm ${action.class || 'btn-outline-primary'}\"\n data-id=\"${this.model.id}\"\n data-action=\"${action.action}\"\n title=\"${action.label || ''}\">\n ${action.icon ? `<i class=\"${action.icon}\"></i>` : ''}\n ${action.label && !action.icon ? action.label : ''}\n </button>\n `;\n }\n return '';\n }).join('');\n\n return `<td><div class=\"btn-group btn-group-sm\">${buttons}</div></td>`;\n }\n\n /**\n * Build context menu cell template\n */\n buildContextMenuTemplate() {\n if (!this.contextMenu || this.contextMenu.length === 0) return '';\n\n return `\n <td class=\"text-end\" style=\"width: 1px;\">\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-link border-0\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n style=\"color: #6c757d;\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end shadow-sm\">\n ${this.buildContextMenuItems()}\n </ul>\n </div>\n </td>\n `;\n }\n\n /**\n * Build context menu items\n */\n buildContextMenuItems() {\n return this.contextMenu.map(menuItem => {\n if (menuItem.separator||menuItem.divider) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n let itemClass = 'dropdown-item';\n if (menuItem.action === 'delete' || menuItem.danger) {\n itemClass += ' text-danger';\n }\n if (menuItem.disabled) {\n itemClass += ' disabled';\n }\n\n return `\n <li>\n <a class=\"${itemClass}\" href=\"#\"\n data-id=\"{{model.id}}\"\n data-action=\"${menuItem.action}\"\n ${menuItem.disabled ? 'aria-disabled=\"true\" tabindex=\"-1\"' : ''}>\n ${menuItem.icon ? `<i class=\"${menuItem.icon} me-2\"></i>` : ''}\n ${menuItem.label}\n </a>\n </li>\n `;\n }).join('');\n }\n\n /**\n * Override onAfterRender to apply function formatters and templates\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Apply function formatters\n this.columns.forEach((column, columnIndex) => {\n if (column.formatter && typeof column.formatter === 'function') {\n let cell = this.element.querySelector(`[data-formatter-id=\"${columnIndex}\"]`);\n if (!cell) {\n // Backward-compatible fallback for existing markup/selectors.\n cell = this.element.querySelector(`[data-formatter=\"${column.key}\"]`);\n }\n if (cell) {\n const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n const context = {\n value,\n row: this.model, // deprecate this\n model: this.model,\n column,\n table: this.tableView,\n index: this.index\n };\n try {\n cell.innerHTML = column.formatter(value, context);\n } catch (error) {\n console.error(`Error formatting cell for column ${column.key}:`, error);\n }\n }\n }\n\n // Apply function templates\n // if (column.template && typeof column.template === 'function') {\n // const cell = this.element.querySelector(`[data-template=\"${column.key}\"]`);\n // if (cell) {\n // const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n // cell.innerHTML = column.template(value, this.model);\n // }\n // }\n });\n\n // Update selection state\n if (this.selected) {\n this.element.classList.add('selected');\n }\n\n // Set data-id attribute for easy identification\n const id = this.model.get ? this.model.get('id') : this.model.id;\n if (id) {\n this.element.setAttribute('data-id', id);\n }\n }\n\n /**\n * Handle edit cell action\n */\n async onActionEditCell(event, element) {\n event.stopPropagation();\n\n const columnKey = element.getAttribute('data-column');\n const column = this.columns.find(col => col.key === columnKey);\n\n if (!column || !column.editable) return;\n\n // Don't enter edit mode if already editing this cell\n if (this.editingCells.has(columnKey)) return;\n\n await this.enterEditMode(columnKey, column, element);\n }\n\n /**\n * Handle row click action\n */\n async onActionRowClick(event, element) {\n // Don't trigger row click if clicking on action buttons or editing\n if (event.target.closest('.btn-group') || event.target.closest('.dropdown') || event.target.closest('.cell-editor')) {\n return;\n }\n\n // Emit row click event\n this.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n\n // Notify parent TableView\n if (this.tableView) {\n this.tableView.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n }\n }\n\n /**\n * Handle view action\n */\n async onActionView(event, element) {\n event.stopPropagation();\n\n this.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Handle edit action\n */\n async onActionEdit(event, element) {\n event.stopPropagation();\n\n this.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n }\n return true;\n }\n\n /**\n * Handle delete action\n */\n async onActionDelete(event, element) {\n event.stopPropagation();\n\n this.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Enter edit mode for a cell\n */\n async enterEditMode(columnKey, column, cellElement) {\n const contentSpan = cellElement.querySelector('.cell-content');\n if (!contentSpan) return;\n\n this.editingCells.add(columnKey);\n const currentValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Create editor based on column configuration\n const editor = this.createCellEditor(column, currentValue);\n\n // Replace content with editor\n const originalContent = contentSpan.innerHTML;\n contentSpan.style.display = 'none';\n\n const editorContainer = document.createElement('div');\n editorContainer.className = 'cell-editor';\n editorContainer.innerHTML = editor;\n cellElement.appendChild(editorContainer);\n\n // Focus the input\n const input = editorContainer.querySelector('input, select, .form-check-input');\n if (input) {\n input.focus();\n if (input.type === 'text' || input.type === 'textarea') {\n input.select();\n }\n }\n\n // Store original content for cancel\n editorContainer.dataset.originalContent = originalContent;\n editorContainer.dataset.columnKey = columnKey;\n\n // Set up event listeners\n this.setupEditorEvents(editorContainer, columnKey, column);\n\n this.emit('cell:edit', {\n row: this,\n model: this.model,\n column: columnKey,\n originalValue: currentValue\n });\n }\n\n /**\n * Create cell editor HTML based on column configuration\n */\n createCellEditor(column, currentValue) {\n const options = column.editableOptions || {};\n\n switch (options.type) {\n case 'select':\n return this.createSelectEditor(options, currentValue);\n case 'switch':\n case 'checkbox':\n return this.createSwitchEditor(options, currentValue);\n case 'textarea':\n return this.createTextareaEditor(options, currentValue);\n default:\n return this.createTextEditor(options, currentValue);\n }\n }\n\n /**\n * Create text input editor\n */\n createTextEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const inputType = options.inputType || 'text';\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <input type=\"${inputType}\"\n class=\"form-control form-control-sm cell-input\"\n value=\"${this.escapeHtml(currentValue || '')}\"\n placeholder=\"${placeholder}\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create textarea editor\n */\n createTextareaEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const rows = options.rows || 2;\n\n return `\n <div class=\"d-flex gap-1\">\n <textarea class=\"form-control form-control-sm cell-input\"\n rows=\"${rows}\"\n placeholder=\"${placeholder}\">${this.escapeHtml(currentValue || '')}</textarea>\n <div class=\"d-flex flex-column gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Create select dropdown editor\n */\n createSelectEditor(options, currentValue) {\n const optionsArray = options.options || [];\n let optionsHtml = '';\n\n optionsArray.forEach(option => {\n if (typeof option === 'string') {\n const selected = option === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option}\" ${selected}>${option}</option>`;\n } else if (typeof option === 'object' && option.value !== undefined) {\n const selected = option.value === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option.value}\" ${selected}>${option.label || option.value}</option>`;\n }\n });\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm cell-input\">\n ${optionsHtml}\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create switch/checkbox editor\n */\n createSwitchEditor(options, currentValue) {\n const checked = currentValue ? 'checked' : '';\n const switchType = options.type === 'switch' ? 'form-switch' : '';\n\n return `\n <div class=\"d-flex gap-2 align-items-center\">\n <div class=\"form-check ${switchType}\">\n <input class=\"form-check-input cell-input\" type=\"checkbox\" ${checked}>\n </div>\n <div class=\"d-flex gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Setup event listeners for cell editor\n */\n setupEditorEvents(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n const saveBtn = editorContainer.querySelector('.cell-save');\n const cancelBtn = editorContainer.querySelector('.cell-cancel');\n\n // Save on Enter (for text inputs)\n if (input && (input.type === 'text' || input.type === 'email' || input.type === 'number')) {\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.saveCellEdit(editorContainer, columnKey, column);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.cancelCellEdit(editorContainer, columnKey);\n }\n });\n }\n\n // Save on change for selects and checkboxes (if auto-save enabled)\n if (input && (input.type === 'checkbox' || input.tagName === 'SELECT') && column.autoSave !== false) {\n input.addEventListener('change', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n }\n\n // Button events\n saveBtn?.addEventListener('click', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n\n cancelBtn?.addEventListener('click', () => {\n this.cancelCellEdit(editorContainer, columnKey);\n });\n }\n\n /**\n * Save cell edit\n */\n async saveCellEdit(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n if (!input) return;\n\n let newValue;\n\n // Extract value based on input type\n if (input.type === 'checkbox') {\n newValue = input.checked;\n } else if (input.tagName === 'SELECT') {\n newValue = input.value;\n } else {\n newValue = input.value;\n }\n\n const oldValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Save to model and backend\n try {\n if (this.model.save) {\n await this.model.save({ [columnKey]: newValue });\n } else {\n // Fallback for models without save method\n this.model[columnKey] = newValue;\n }\n\n // Exit edit mode\n this.exitEditMode(editorContainer, columnKey, newValue);\n\n // Emit save event\n this.emit('cell:save', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue\n });\n\n } catch (error) {\n // Show error and keep in edit mode\n console.error('Failed to save cell edit:', error);\n this.emit('cell:save:error', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue,\n error: error\n });\n\n // Could show an error message in the UI\n editorContainer.classList.add('saving-error');\n setTimeout(() => editorContainer.classList.remove('saving-error'), 3000);\n }\n }\n\n /**\n * Cancel cell edit\n */\n cancelCellEdit(editorContainer, columnKey) {\n const originalContent = editorContainer.dataset.originalContent;\n this.exitEditMode(editorContainer, columnKey, null, originalContent);\n\n this.emit('cell:cancel', {\n row: this,\n model: this.model,\n column: columnKey\n });\n }\n\n /**\n * Exit edit mode and restore content\n */\n exitEditMode(editorContainer, columnKey, newValue = null, originalContent = null) {\n const cellElement = editorContainer.closest('td');\n const contentSpan = cellElement.querySelector('.cell-content');\n\n if (contentSpan) {\n if (newValue !== null) {\n // Update display with new value (with proper formatting if needed)\n const column = this.columns.find(col => col.key === columnKey);\n let displayValue = newValue;\n\n if (column && column.formatter && typeof column.formatter === 'string') {\n displayValue = dataFormatter.pipe(newValue, column.formatter);\n }\n\n contentSpan.innerHTML = this.escapeHtml(displayValue);\n } else if (originalContent) {\n // Restore original content on cancel\n contentSpan.innerHTML = originalContent;\n }\n\n contentSpan.style.display = '';\n }\n\n // Remove editor\n editorContainer.remove();\n this.editingCells.delete(columnKey);\n }\n\n /**\n * Escape HTML for safe display\n */\n escapeHtml(text) {\n if (text === null || text === undefined) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Override select to handle table-specific selection UI\n */\n select() {\n super.select();\n this.addClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.add('selected');\n }\n }\n\n /**\n * Override deselect to handle table-specific selection UI\n */\n deselect() {\n super.deselect();\n this.removeClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.remove('selected');\n }\n }\n}\n\nexport default TableRow;\n","/**\n * DjangoLookups - Utility for Django-style filter lookup parsing and formatting\n * \n * Provides utilities to parse filter keys like \"status__in\" or \"created__gte\"\n * and format them into human-readable display text for filter pills.\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * formatFilterDisplay('status__in', 'new,open', 'Status') // \"Status in 'new', 'open'\"\n */\n\n/**\n * Supported Django-style lookups with display configurations\n * Only includes commonly used lookups (KISS principle)\n */\nexport const LOOKUPS = {\n // Comparison\n 'exact': { \n display: 'is',\n description: 'Exact match'\n },\n 'in': { \n display: 'in',\n description: 'Match any of the values (comma-separated)'\n },\n 'not': { \n display: 'is not',\n description: 'Does not match'\n },\n 'not_in': { \n display: 'not in',\n description: 'Does not match any of the values'\n },\n 'gt': { \n display: '>',\n description: 'Greater than'\n },\n 'gte': { \n display: '>=',\n description: 'Greater than or equal to'\n },\n 'lt': { \n display: '<',\n description: 'Less than'\n },\n 'lte': { \n display: '<=',\n description: 'Less than or equal to'\n },\n \n // String operations\n 'contains': { \n display: 'contains',\n description: 'Contains substring (case-sensitive)'\n },\n 'icontains': { \n display: 'contains',\n description: 'Contains substring (case-insensitive)'\n },\n 'startswith': { \n display: 'starts with',\n description: 'Starts with substring (case-sensitive)'\n },\n 'istartswith': { \n display: 'starts with',\n description: 'Starts with substring (case-insensitive)'\n },\n 'endswith': { \n display: 'ends with',\n description: 'Ends with substring (case-sensitive)'\n },\n 'iendswith': { \n display: 'ends with',\n description: 'Ends with substring (case-insensitive)'\n },\n \n // Null checks\n 'isnull': { \n display: (val) => val === 'true' || val === true ? 'is null' : 'is not null',\n description: 'Check if value is null or not'\n },\n \n // Range operations\n 'range': { \n display: 'between',\n description: 'Between two values (comma-separated)'\n }\n};\n\n/**\n * Parse a filter key into field name and lookup operator\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\", \"created__gte\")\n * @returns {Object} Object with field and lookup properties\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * parseFilterKey('status') // { field: 'status', lookup: null }\n * parseFilterKey('user__profile__name__icontains') // { field: 'user__profile__name', lookup: 'icontains' }\n */\nexport function parseFilterKey(paramKey) {\n if (!paramKey || typeof paramKey !== 'string') {\n return { field: paramKey, lookup: null };\n }\n\n const parts = paramKey.split('__');\n \n // Single part, no lookup\n if (parts.length === 1) {\n return { field: paramKey, lookup: null };\n }\n \n // Check if last part is a valid lookup\n const possibleLookup = parts[parts.length - 1];\n if (LOOKUPS[possibleLookup]) {\n return { \n field: parts.slice(0, -1).join('__'), \n lookup: possibleLookup \n };\n }\n \n // No valid lookup found, treat entire string as field name\n return { field: paramKey, lookup: null };\n}\n\n/**\n * Format a filter key and value into human-readable display text\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\")\n * @param {string|Array} value - Filter value(s)\n * @param {string} label - Human-readable field label\n * @returns {string} Formatted display text\n * \n * @example\n * formatFilterDisplay('status__in', 'new,open', 'Status') \n * // \"Status in 'new', 'open'\"\n * \n * formatFilterDisplay('created__gte', '2025-01-01', 'Created') \n * // \"Created >= '2025-01-01'\"\n * \n * formatFilterDisplay('name__icontains', 'john', 'Name') \n * // \"Name contains 'john'\"\n */\nexport function formatFilterDisplay(paramKey, value, label) {\n if (!paramKey || value === null || value === undefined) {\n return '';\n }\n\n const { field, lookup } = parseFilterKey(paramKey);\n const lookupDef = LOOKUPS[lookup];\n \n // Handle object-based values (e.g., daterange payloads)\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const hasStart = value.start !== undefined && value.start !== null && value.start !== '';\n const hasEnd = value.end !== undefined && value.end !== null && value.end !== '';\n\n if (hasStart || hasEnd) {\n if (hasStart && hasEnd) {\n return `${label} between '${value.start}' and '${value.end}'`;\n }\n if (hasStart) {\n return `${label} from '${value.start}'`;\n }\n return `${label} until '${value.end}'`;\n }\n\n // Fallback to JSON if it's some other object shape\n return `${label} is '${JSON.stringify(value)}'`;\n }\n\n // Convert array to comma-separated string if needed\n const valueStr = Array.isArray(value) ? value.join(',') : String(value);\n \n // No lookup or exact lookup - simple \"is\" format\n if (!lookup || lookup === 'exact') {\n return `${label} is '${valueStr}'`;\n }\n \n // Multi-value lookups (in, not_in)\n if (lookup === 'in' || lookup === 'not_in') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 0) {\n return `${label} ${lookupDef.display}`;\n }\n const formattedValues = values.map(v => `'${v}'`).join(', ');\n return `${label} ${lookupDef.display} ${formattedValues}`;\n }\n \n // Range lookup - special formatting\n if (lookup === 'range') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 2) {\n return `${label} between '${values[0]}' and '${values[1]}'`;\n }\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Null check - dynamic display based on value\n if (lookup === 'isnull') {\n const displayText = typeof lookupDef.display === 'function' \n ? lookupDef.display(valueStr) \n : lookupDef.display;\n return `${label} ${displayText}`;\n }\n \n // Standard lookup with operator\n if (lookupDef) {\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Fallback for unknown lookups\n return `${label} is '${valueStr}'`;\n}\n\n/**\n * Get a user-friendly description of a lookup operator\n * \n * @param {string} lookup - Lookup operator (e.g., \"in\", \"gte\", \"icontains\")\n * @returns {string} Human-readable description\n * \n * @example\n * getLookupDescription('in') // \"Match any of the values (comma-separated)\"\n * getLookupDescription('gte') // \"Greater than or equal to\"\n */\nexport function getLookupDescription(lookup) {\n const lookupDef = LOOKUPS[lookup];\n return lookupDef ? lookupDef.description : 'Exact match';\n}\n\n/**\n * Check if a string is a valid lookup operator\n * \n * @param {string} lookup - Potential lookup operator\n * @returns {boolean} True if valid lookup\n * \n * @example\n * isValidLookup('in') // true\n * isValidLookup('foo') // false\n */\nexport function isValidLookup(lookup) {\n return lookup && LOOKUPS.hasOwnProperty(lookup);\n}\n\n/**\n * Get all available lookup operators\n * \n * @returns {Array<string>} Array of lookup operator names\n * \n * @example\n * getAvailableLookups() // ['exact', 'in', 'not', 'not_in', 'gt', ...]\n */\nexport function getAvailableLookups() {\n return Object.keys(LOOKUPS);\n}\n\n/**\n * Build a filter key from field name and lookup operator\n * \n * @param {string} field - Field name\n * @param {string} lookup - Lookup operator (optional)\n * @returns {string} Combined filter key\n * \n * @example\n * buildFilterKey('status', 'in') // \"status__in\"\n * buildFilterKey('status') // \"status\"\n */\nexport function buildFilterKey(field, lookup = null) {\n if (!field) return '';\n if (!lookup) return field;\n return `${field}__${lookup}`;\n}\n\nexport default {\n LOOKUPS,\n parseFilterKey,\n formatFilterDisplay,\n getLookupDescription,\n isValidLookup,\n getAvailableLookups,\n buildFilterKey\n};\n","/**\n * TableView - Advanced data table component extending ListView\n *\n * Leverages ListView's view management system for efficient row rendering.\n * Each row is a separate TableRow view that only re-renders when its model changes.\n *\n * @example\n * const table = new TableView({\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email', visibility: 'md' }, // Hidden on xs/sm, visible md+\n * { key: 'phone', label: 'Phone', visibility: 'lg' }, // Visible only on lg+\n * { key: 'created', label: 'Created', formatter: 'date', visibility: 'xl' } // Visible only on xl+\n * ],\n * actions: ['view', 'edit', 'delete'],\n * selectionMode: 'multiple'\n * });\n */\n\nimport ListView from '../list/ListView.js';\nimport TableRow from './TableRow.js';\nimport Mustache from '@core/utils/mustache.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport FormView from '@core/forms/FormView.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\nimport { parseFilterKey, formatFilterDisplay } from '@core/utils/DjangoLookups.js';\n\nclass TableView extends ListView {\n constructor(options = {}) {\n // Set up table-specific defaults before calling super\n const tableOptions = {\n className: 'table-view-component',\n itemClass: options.itemClass || TableRow,\n selectionMode: options.selectable ? 'multiple' : 'none',\n emptyMessage: options.emptyMessage || 'No data available',\n addButtonIcon: options.addButtonIcon || 'bi bi-plus-circle',\n ...options\n };\n\n super(tableOptions);\n\n // Fullscreen state\n this.isFullscreen = false;\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.searchable = options.searchable !== false;\n this.sortable = options.sortable !== false;\n this.filterable = options.filterable !== false;\n this.paginated = options.paginated !== false;\n this.clickAction = options.clickAction || \"view\";\n\n // Model operation configurations\n this.itemView = options.itemView;\n this.addForm = options.addForm;\n this.editForm = options.editForm;\n this.deleteTemplate = options.deleteTemplate;\n this.formDialogConfig = options.formDialogConfig || {};\n this.viewDialogOptions = options.viewDialogOptions || {};\n\n // Export configuration\n this.exportOptions = options.exportOptions || null;\n if (this.options.showExport && !this.exportOptions) {\n this.exportOptions = [\n { format: 'csv', label: 'Export as CSV', icon: 'bi bi-file-earmark-spreadsheet' },\n { format: 'json', label: 'Export as JSON', icon: 'bi bi-file-earmark-code' }\n ];\n }\n this.exportSource = options.exportSource || 'remote';\n\n // Filter configuration\n this.filters = {};\n this.additionalFilters = options.filters || [];\n this.hideActivePills = options.hideActivePills || false;\n this.hideActivePillNames = options.hideActivePillNames || [];\n this.rowAction = options.rowAction || \"row-click\";\n this.batchBarLocation = options.batchBarLocation || \"bottom\"; // \"top\" or \"bottom\"\n\n this.options.addButtonLabel = options.addButtonLabel || 'Add';\n\n // Custom toolbar buttons\n this.toolbarButtons = options.toolbarButtons || [];\n\n // Table display options\n this.tableOptions = {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n size: null, // null, 'sm', 'lg'\n ...options.tableOptions\n };\n\n // Search configuration\n this.searchPlacement = options.searchPlacement || 'toolbar'; // 'toolbar' or 'dropdown'\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n\n // Initialize column configuration BEFORE building template\n this.initializeColumns();\n\n // Extract filters from columns BEFORE building template\n this.extractColumnFilters();\n\n // Detect columns that need footer totals\n this.footerTotalColumns = this.columns.filter(col => col.footer_total === true);\n this.hasFooterTotals = this.footerTotalColumns.length > 0;\n\n // Build template with Mustache variables\n this.template = this.buildTableTemplate();\n\n // Listen for collection changes to update totals\n this.setupCollectionListeners();\n }\n\n /**\n * Setup collection event listeners for totals updates\n */\n setupCollectionListeners() {\n if (this.hasFooterTotals && this.collection) {\n // Re-render totals when collection data changes\n this.collection.on('reset add remove change', () => {\n this.updateFooterTotals();\n });\n }\n }\n\n /**\n * Initialize column configuration\n */\n initializeColumns() {\n this.columns.forEach(column => {\n // Ensure each column has a key\n if (!column.key && column.name) {\n column.key = column.name;\n }\n\n // Set default label if not provided\n if (!column.label && !column.title) {\n column.label = column.key.charAt(0).toUpperCase() + column.key.slice(1);\n }\n });\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Extract column key and formatter from combined key (e.g., \"sales_amount|currency\")\n */\n parseColumnKey(key) {\n const parts = key.split('|');\n return {\n fieldKey: parts[0],\n formatter: parts[1] || null\n };\n }\n\n /**\n * Update footer totals in the DOM without full re-render\n */\n updateFooterTotals() {\n if (!this.hasFooterTotals || !this.element) return;\n\n const totals = this.calculateFooterTotals();\n console.log('Updating footer totals in DOM:', totals);\n\n // Update each total cell in the footer\n let totalColumnIndex = 0;\n this.columns.forEach((column) => {\n if (column.footer_total) {\n const safeKey = `col_${totalColumnIndex}`;\n const cell = this.element.querySelector(`[data-total-column=\"${safeKey}\"]`);\n\n if (cell && totals[safeKey]) {\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n let displayValue;\n\n if (formatter && typeof formatter === 'string') {\n // Use DataFormatter if available\n displayValue = this.formatValue(totals[safeKey].value, formatter);\n } else {\n displayValue = totals[safeKey].value;\n }\n\n cell.textContent = displayValue;\n }\n totalColumnIndex++;\n }\n });\n }\n\n /**\n * Format a value using DataFormatter\n */\n formatValue(value, formatter) {\n try {\n return dataFormatter.pipe(value, formatter);\n } catch (e) {\n console.warn('Error formatting value:', e);\n return value;\n }\n }\n\n /**\n * Calculate totals for footer columns\n */\n calculateFooterTotals() {\n if (!this.hasFooterTotals || !this.collection || this.collection.length === 0) {\n return {};\n }\n\n const totals = {};\n\n this.footerTotalColumns.forEach((column, totalColumnIndex) => {\n const { fieldKey, formatter } = this.parseColumnKey(column.key);\n let sum = 0;\n\n // Sum values from all items in collection\n this.collection.forEach(model => {\n const value = model.get ? model.get(fieldKey) : model[fieldKey];\n const numValue = parseFloat(value) || 0;\n sum += numValue;\n });\n\n // Debug logging\n console.log(`Footer total for ${column.key}: ${sum} (from ${this.collection.length} items)`);\n\n // Use safe key for Mustache (avoid special characters)\n const safeKey = `col_${totalColumnIndex}`;\n\n // Store total with formatter info\n totals[safeKey] = {\n value: sum,\n formatter: formatter || column.formatter,\n fieldKey: fieldKey,\n originalKey: column.key\n };\n });\n\n return totals;\n }\n\n /**\n * Extract filters from column configuration\n */\n extractColumnFilters() {\n this.filters = {};\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n this.filters[fieldKey] = column.filter;\n }\n });\n }\n\n isSelectable() {\n return this.batchActions && this.batchActions.length > 0 && this.selectionMode == 'multiple';\n }\n\n /**\n * Build the complete table template\n */\n buildTableTemplate() {\n const batchPanelTop = this.batchBarLocation === 'top' ? this.buildBatchActionsPanel() : '';\n const batchPanelBottom = this.batchBarLocation === 'bottom' ? this.buildBatchActionsPanel() : '';\n\n return `\n <div class=\"mojo-table-wrapper\">\n ${this.buildToolbarTemplate()}\n ${batchPanelTop}\n <div class=\"table-container\"${(() => { const __fs = (this.tableOptions && this.tableOptions.fontSize != null) ? this.tableOptions.fontSize : (this.options && this.options.fontSize); const __val = __fs === 'sm' ? '0.9rem' : (__fs === 'xs' ? '0.8rem' : (__fs ? String(__fs) : null)); return __val ? ` style=\"font-size: ${__val};\"` : ''; })()}>\n {{#loading}}\n <div class=\"mojo-table-loading d-flex justify-content-center align-items-center py-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"table-empty text-center py-5\">\n <i class=\"bi bi-inbox fa-2x mb-2 text-muted\"></i>\n <p class=\"text-muted\">{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <table class=\"${this.buildTableClasses()}\">\n ${this.buildTableHeaderTemplate()}\n <tbody data-container=\"items\"></tbody>\n ${this.hasFooterTotals ? this.buildTableFooterTemplate() : ''}\n </table>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${batchPanelBottom}\n ${this.buildPaginationTemplate()}\n </div>\n `;\n }\n\n /**\n * Build table CSS classes\n */\n buildTableClasses() {\n let classes = ['table'];\n\n if (this.tableOptions.striped) classes.push('table-striped');\n if (this.tableOptions.bordered) classes.push('table-bordered');\n if (this.tableOptions.hover) classes.push('table-hover');\n if (this.tableOptions.responsive) classes.push('table-responsive');\n if (this.tableOptions.background) classes.push(`table-${this.tableOptions.background}`);\n if (this.tableOptions.size === 'sm') classes.push('table-sm');\n if (this.tableOptions.size === 'lg') classes.push('table-lg');\n\n return classes.join(' ');\n }\n\n /**\n * Build toolbar template\n */\n buildToolbarTemplate() {\n if (!this.searchable && !this.filterable) {\n return '';\n }\n\n return `\n <div class=\"table-action-buttons mb-3\">\n <div class=\"d-flex align-items-center gap-2\">\n ${this.buildActionButtonsTemplate()}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n\n </div>\n <div data-container=\"filter-pills\"></div>\n </div>\n `;\n }\n\n /**\n * Build action buttons template\n */\n buildActionButtonsTemplate() {\n let buttons = [];\n\n // Refresh button\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-refresh\"\n data-action=\"refresh\"\n title=\"Refresh\">\n <i class=\"bi bi-arrow-clockwise\"></i>\n </button>\n `);\n\n // Fullscreen button (only if browser supports it)\n if (this.isFullscreenSupported()) {\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-fullscreen\"\n data-action=\"toggle-fullscreen\"\n title=\"Toggle Fullscreen\">\n <i class=\"bi bi-fullscreen\"></i>\n </button>\n `);\n }\n\n // Custom action buttons from options\n if (this.options.showAdd) {\n buttons.push(`\n <button class=\"btn btn-sm btn-success btn-add\"\n data-action=\"add\"\n title=\"${this.options.addButtonLabel}\">\n <i class=\"${this.options.addButtonIcon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${this.options.addButtonLabel}</span>\n </button>\n `);\n }\n\n if (this.options.showExport) {\n if (this.exportOptions && this.exportOptions.length > 1) {\n // Dropdown for multiple export options\n const dropdownItems = this.exportOptions.map(opt => `\n <li>\n <a class=\"dropdown-item\" href=\"#\" data-action=\"export\" data-format=\"${opt.format}\">\n <i class=\"${opt.icon || 'bi bi-file-earmark-arrow-down'} me-2\"></i>${opt.label}\n </a>\n </li>\n `).join('');\n\n buttons.push(`\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\" title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n <ul class=\"dropdown-menu\">\n ${dropdownItems}\n </ul>\n </div>\n `);\n } else {\n // Single export button\n const format = this.exportOptions && this.exportOptions.length === 1 ? this.exportOptions[0].format : 'json';\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-export\"\n data-action=\"export\"\n data-format=\"${format}\"\n title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n `);\n }\n }\n\n // if (buttons.length > 0) {\n // buttons.push(`<div class=\"vr mx-2\"></div>`);\n // }\n\n // Render custom toolbar buttons\n if (this.toolbarButtons && this.toolbarButtons.length > 0) {\n this.toolbarButtons.forEach((button, index) => {\n const {\n label = 'Button',\n icon = '',\n action = '',\n handler = null,\n variant = 'outline-secondary',\n title = label,\n className = '',\n permissions = null\n } = button;\n\n // Check permissions if specified\n if (permissions && !this.checkPermissions(permissions)) {\n return;\n }\n\n const iconHtml = icon ? `<i class=\"${icon} me-1\"></i>` : '';\n const labelHtml = `<span class=\"d-none d-lg-inline\">${label}</span>`;\n\n // Use handler if provided, otherwise use action for data-action attribute\n let dataAttrs = '';\n if (handler) {\n dataAttrs = `data-action=\"custom-toolbar-button\" data-button-index=\"${index}\"`;\n } else if (action) {\n dataAttrs = `data-action=\"${action}\"`;\n }\n\n const btnClass = `btn btn-sm btn-${variant} ${className}`.trim();\n\n buttons.push(`\n <button class=\"${btnClass}\"\n ${dataAttrs}\n title=\"${title}\">\n ${iconHtml}${labelHtml}\n </button>\n `);\n });\n }\n\n return buttons.join('');\n }\n\n /**\n * Build search template\n */\n buildSearchTemplate() {\n return `\n <div class=\"flex-grow-1\" style=\"max-width: 400px;\">\n <div class=\"input-group input-group-sm\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input type=\"search\"\n class=\"form-control\"\n placeholder=\"{{searchPlaceholder}}\"\n data-filter=\"search\"\n data-change-action=\"apply-search\"\n value=\"{{collection.params.search}}\"\n aria-label=\"Search\">\n {{#searchValue}}\n <button class=\"btn btn-outline-secondary\" type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\">\n <i class=\"bi bi-x\"></i>\n </button>\n {{/searchValue}}\n </div>\n </div>\n `;\n }\n\n /**\n * Build filter dropdown template\n */\n buildFilterDropdownTemplate() {\n const hasFilters = (this.filters && Object.keys(this.filters).length > 0) ||\n (this.additionalFilters && this.additionalFilters.length > 0);\n\n if (!hasFilters) {\n return '';\n }\n\n return `\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"bi bi-filter me-1\"></i>\n <span class=\"d-none d-lg-inline\">Add Filter</span>\n </button>\n <div class=\"dropdown-menu\" style=\"min-width: 250px;\">\n ${this.buildFilterList()}\n </div>\n </div>\n `;\n }\n\n /**\n * Build simple filter selection list\n */\n buildFilterList() {\n const allFilters = this.getAllAvailableFilters();\n const activeFilters = this.getActiveFilters();\n\n if (allFilters.length === 0) {\n return '<div class=\"dropdown-item-text text-muted\">No filters available</div>';\n }\n\n const filterItems = allFilters.map(filter => {\n const isActive = activeFilters.hasOwnProperty(filter.key);\n const activeClass = isActive ? 'active' : '';\n const icon = this.getFilterIcon(filter.type || filter.config?.type);\n\n return `\n <button class=\"dropdown-item ${activeClass}\"\n data-action=\"add-filter\"\n data-filter-key=\"${filter.key}\">\n <i class=\"bi bi-${icon} me-2\"></i>\n ${filter.label}\n ${isActive ? '<i class=\"bi bi-check-circle ms-auto\"></i>' : ''}\n </button>\n `;\n }).join('');\n\n return `\n ${filterItems}\n ${Object.keys(activeFilters).length > 0 ? `\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item text-danger\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-2\"></i>Clear All Filters\n </button>\n ` : ''}\n `;\n }\n\n /**\n * Update filter pills in the DOM\n */\n updateFilterPills() {\n const container = this.element?.querySelector('[data-container=\"filter-pills\"]');\n\n if (!container) {\n return;\n }\n\n const activeFilters = this.getActiveFilters();\n\n const pillsHTML = this.buildActivePills();\n container.innerHTML = pillsHTML;\n }\n\n /**\n * Update search input value across all search inputs\n */\n updateSearchInputs(value) {\n const searchInputs = this.element?.querySelectorAll('[data-filter=\"search\"]');\n if (searchInputs) {\n searchInputs.forEach(input => {\n input.value = value || '';\n });\n }\n }\n\n /**\n * Build active filter pills display\n */\n buildActivePills() {\n if (this.hideActivePills) {\n return '';\n }\n\n const activeFilters = this.getActiveFilters();\n const hasSearch = activeFilters.search && activeFilters.search.toString().trim() !== '';\n let filterEntries = Object.entries(activeFilters).filter(([key, value]) =>\n value && value.toString().trim() !== '' && key !== 'search'\n );\n\n // Hide specific pills based on configuration\n if (this.hideActivePillNames && this.hideActivePillNames.length > 0) {\n filterEntries = filterEntries.filter(([key]) =>\n !this.hideActivePillNames.includes(key)\n );\n }\n\n if (filterEntries.length === 0 && !hasSearch) {\n return '';\n }\n\n const pills = filterEntries.map(([paramKey, value]) => {\n const { field } = parseFilterKey(paramKey);\n const label = this.getFilterLabel(field);\n const displayText = formatFilterDisplay(paramKey, value, label);\n const icon = 'filter'; // search won't appear as pill anymore\n\n return `\n <span class=\"badge bg-primary me-1 mb-1 py-1 px-2 position-relative\" style=\"font-size: 0.75rem;\">\n <i class=\"bi bi-${icon} me-1\" style=\"font-size: 0.65rem;\"></i>\n\n <button type=\"button\" class=\"btn btn-link text-white p-0 ms-1\"\n style=\"font-size: 0.65rem; line-height: 1;\"\n data-action=\"edit-filter\"\n data-filter=\"${paramKey}\"\n title=\"Edit filter\">\n ${displayText}\n </button>\n\n <button type=\"button\" class=\"btn-close btn-close-white ms-1\"\n style=\"font-size: 0.6rem; width: 0.5rem; height: 0.5rem;\"\n data-action=\"remove-filter\"\n data-filter=\"${paramKey}\"\n title=\"Remove filter\">\n </button>\n </span>\n `;\n }).join('');\n\n // Show Clear All if there are multiple filters, or any filter + search\n const showClearAll = filterEntries.length > 1 || (filterEntries.length > 0 && hasSearch) || (filterEntries.length === 0 && hasSearch);\n const clearAllButton = showClearAll ? `\n <button class=\"btn btn-sm btn-outline-secondary mb-1 py-0 px-2\" style=\"font-size: 0.75rem;\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-1\" style=\"font-size: 0.7rem;\"></i>\n <small>Clear All</small>\n </button>\n ` : '';\n\n return `\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"d-flex flex-wrap align-items-center\">\n ${pills}\n ${clearAllButton}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Build table header template\n */\n buildTableHeaderTemplate() {\n let headerCells = '';\n\n // Selection checkbox header\n if (this.isSelectable()) {\n headerCells += `\n <th style=\"width: 40px; padding: 0;\">\n <div class=\"mojo-select-all-cell\" data-action=\"select-all\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </th>\n `;\n }\n\n // Column headers\n this.columns.forEach(column => {\n // Parse column key to get field name without pipes/formatters\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const sortable = this.sortable && column.sortable !== false;\n const currentSort = this.getSortBy() === fieldKey ? this.getSortDirection() : null;\n const sortIcon = this.getSortIcon(currentSort);\n const label = column.label || column.title || fieldKey;\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n const sortDropdown = sortable ? `\n <div class=\"dropdown d-inline-block ms-2\">\n <button class=\"btn btn-sm btn-link p-0 text-decoration-none\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\"\n data-column=\"${fieldKey}\">\n ${sortIcon}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n <li><a class=\"dropdown-item ${currentSort === 'asc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"asc\">\n <i class=\"bi bi-sort-alpha-down me-2\"></i>Sort A-Z\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === 'desc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"desc\">\n <i class=\"bi bi-sort-alpha-down-alt me-2\"></i>Sort Z-A\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === null ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"none\">\n <i class=\"bi bi-x-circle me-2\"></i>No Sort\n </a></li>\n </ul>\n </div>\n ` : '';\n\n headerCells += `\n <th class=\"${sortable ? 'sortable' : ''} ${responsiveClasses}\">\n <div class=\"d-flex align-items-center\">\n <span>${label}</span>\n ${sortDropdown}\n </div>\n </th>\n `;\n });\n\n // Actions header\n if (this.actions) {\n headerCells += '<th>Actions</th>';\n } else if (this.contextMenu) {\n headerCells += '<th style=\"width: 1px;\"></th>';\n }\n\n return `\n <thead>\n <tr>\n ${headerCells}\n </tr>\n </thead>\n `;\n }\n\n /**\n * Build table footer template with totals\n */\n buildTableFooterTemplate() {\n let footerCells = '';\n\n // Selection checkbox footer (empty)\n if (this.isSelectable()) {\n footerCells += '<td></td>';\n }\n\n // Column footers\n let totalColumnIndex = 0;\n this.columns.forEach((column, index) => {\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n if (column.footer_total) {\n // Use safe key for Mustache template\n const safeKey = `col_${totalColumnIndex}`;\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n\n let cellContent;\n if (formatter && typeof formatter === 'string') {\n cellContent = `{{{footerTotals.${safeKey}.value|${formatter}}}}`;\n } else {\n cellContent = `{{footerTotals.${safeKey}.value}}`;\n }\n\n footerCells += `<td class=\"table-footer-total ${responsiveClasses}\" data-total-column=\"${safeKey}\">${cellContent}</td>`;\n totalColumnIndex++;\n } else if (index === 0) {\n // First column shows \"Totals\" label\n footerCells += `<td class=\"table-footer-label ${responsiveClasses}\"><strong>Totals</strong></td>`;\n } else {\n // Empty cell for non-total columns\n footerCells += `<td class=\"${responsiveClasses}\"></td>`;\n }\n });\n\n // Actions footer (empty)\n if (this.actions) {\n footerCells += '<td></td>';\n } else if (this.contextMenu) {\n footerCells += '<td></td>';\n }\n\n return `\n <tfoot>\n <tr class=\"table-totals-row\">\n ${footerCells}\n </tr>\n </tfoot>\n `;\n }\n\n /**\n * Build batch actions panel\n */\n buildBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) {\n return '';\n }\n\n if (this.batchBarLocation === 'top') {\n // Toolbar-style batch actions for top placement\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"batch-${action.action}\" title=\"${action.label}\">\n <i class=\"${action.icon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${action.label}</span>\n </button>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel-top alert alert-info d-none mb-3\" role=\"alert\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"me-2\">\n <span class=\"batch-select-count\">0</span> ${this.options.batchPanelTitle || 'items'} selected\n </strong>\n </div>\n <div class=\"d-flex gap-2 align-items-center\">\n ${actionsHTML}\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"clear-selection\" title=\"Clear Selection\">\n <i class=\"bi bi-x-circle me-1\"></i>\n <span class=\"d-none d-lg-inline\">Clear</span>\n </button>\n </div>\n </div>\n </div>\n `;\n } else {\n // Original bottom panel style\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <div class=\"batch-select-action text-center px-2\" data-action=\"batch-${action.action}\">\n <div class=\"batch-action-icon fs-3\">\n <i class=\"${action.icon}\"></i>\n </div>\n <div class=\"batch-action-title small\">${action.label}</div>\n </div>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel rounded-start rounded-end\" style=\"display: none;\">\n <div class=\"batch-select-panel rounded-start rounded-end\">\n <div class=\"row g-0\">\n <div class=\"col-auto\">\n <div class=\"batch-select-count rounded-start\">0</div>\n </div>\n <div class=\"col\">\n <div class=\"ps-2 batch-select-title\">${this.options.batchPanelTitle || 'Rows'}</div>\n </div>\n <div class=\"col\">\n <div class=\"batch-select-actions d-flex justify-content-end\">\n ${actionsHTML}\n </div>\n </div>\n <div class=\"col-auto\">\n <div class=\"batch-select-end rounded-end\"></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n }\n\n /**\n * Build pagination template\n */\n buildPaginationTemplate() {\n if (!this.paginated) {\n return '';\n }\n\n return `\n <div class=\"table-status-bar mt-3\">\n <div class=\"d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3\">\n <div class=\"d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start\">\n <span class=\"text-muted\">\n Showing <span data-value=\"start\">0</span> to <span data-value=\"end\">0</span>\n of <span data-value=\"total\">0</span> entries\n </span>\n <div class=\"d-flex align-items-center\">\n <label class=\"form-label me-2 mb-0\">Show:</label>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\" data-change-action=\"page-size\">\n <option value=\"5\">5</option>\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n </div>\n <nav aria-label=\"Table pagination\">\n <ul class=\"pagination pagination-sm mb-0 justify-content-center\" data-container=\"pagination\">\n <!-- Pagination will be rendered here -->\n </ul>\n </nav>\n </div>\n </div>\n `;\n }\n\n /**\n * Override _createItemView to pass table-specific options\n */\n _createItemView(model, index) {\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n tableView: this, // Also pass as tableView for clarity\n template: this.itemTemplate,\n columns: this.columns,\n actions: this.actions,\n contextMenu: this.contextMenu,\n batchActions: this.batchActions,\n containerId: 'items'\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', (event) => {\n this._onItemSelect(event);\n this.updateBatchActionsPanel();\n });\n itemView.on('item:deselect', (event) => {\n this._onItemDeselect(event);\n this.updateBatchActionsPanel();\n });\n\n // Table-specific row events\n itemView.on('row:click', this._onRowClick.bind(this));\n itemView.on('row:view', this._onRowView.bind(this));\n itemView.on('row:edit', this._onRowEdit.bind(this));\n itemView.on('row:delete', this._onRowDelete.bind(this));\n itemView.on('cell:edit', this._onCellEdit.bind(this));\n itemView.on('cell:save', this._onCellSave.bind(this));\n itemView.on('cell:cancel', this._onCellCancel.bind(this));\n\n return itemView;\n }\n\n /**\n * Override onMounted to ensure filter pills are shown on initial load\n */\n async onMounted() {\n await super.onMounted();\n const activeFilters = this.getActiveFilters();\n\n // Ensure filter pills are displayed if there are active filters from URL\n if (this.collection && Object.keys(activeFilters).length > 0) {\n this.updateFilterPills();\n }\n\n // Add listener for native search clear button\n this.setupSearchClearListener();\n }\n\n /**\n * Setup listener for native search clear (X) button\n */\n setupSearchClearListener() {\n if (!this.element) return;\n\n const searchInputs = this.element.querySelectorAll('input[type=\"search\"][data-filter=\"search\"]');\n searchInputs.forEach(input => {\n // Listen for input event to detect native clear\n input.addEventListener('input', (event) => {\n // If value is empty and we had a search before, it was cleared\n if (event.target.value === '' && this.getActiveFilters().search) {\n this.onActionClearSearch(event, event.target);\n }\n });\n });\n }\n\n /**\n * Handle row click event\n */\n _onRowClick(event) {\n this.emit('row:click', event);\n\n // Default behavior - show item details if configured\n if (this.options.onRowClick) {\n return this.options.onRowClick(event.model, event.event);\n }\n\n if (this.clickAction === 'view') {\n this._onRowView(event);\n } else if (this.clickAction === 'edit') {\n this._onRowEdit(event);\n }\n }\n\n /**\n * Get the Model class from collection or instance\n */\n getModelClass(model) {\n // Try to get from collection first\n if (this.collection?.ModelClass) return this.collection.ModelClass;\n if (this.collection?.model) return this.collection.model;\n\n // Try to get from a model instance\n if (model?.constructor) return model.constructor;\n\n // Return null if we can't determine\n return null;\n }\n\n /**\n * Get model name for display\n */\n getModelName(model) {\n const ModelClass = this.getModelClass(model);\n if (!ModelClass) return 'Item';\n\n return ModelClass.MODEL_NAME ||\n ModelClass.name.replace(/Model$/, '') ||\n 'Item';\n }\n\n /**\n * Resolve item view class with fallbacks\n */\n getItemViewClass(model) {\n // Check instance options first\n if (this.itemView) return this.itemView;\n\n // Check Model class static property\n const ModelClass = this.getModelClass(model);\n if (ModelClass?.VIEW_CLASS) return ModelClass.VIEW_CLASS;\n\n return null; // Will use data view as fallback\n }\n\n /**\n * Resolve add form configuration with fallbacks\n */\n getAddFormConfig(ModelClass) {\n return this.addForm ||\n ModelClass?.ADD_FORM ||\n this.editForm ||\n ModelClass?.EDIT_FORM;\n }\n\n /**\n * Resolve edit form configuration with fallbacks\n */\n getEditFormConfig(ModelClass) {\n return this.editForm ||\n ModelClass?.EDIT_FORM ||\n this.addForm ||\n ModelClass?.ADD_FORM;\n }\n\n /**\n * Get form dialog configuration\n */\n getFormDialogConfig(ModelClass) {\n return {\n ...ModelClass?.FORM_DIALOG_CONFIG,\n ...this.formDialogConfig\n };\n }\n\n /**\n * Render a template string with model context\n */\n renderTemplateString(template, model) {\n if (!template) return '';\n\n // Use Mustache to render the template with the model as context\n return Mustache.render(template, model);\n }\n\n /**\n * Handle row view action\n */\n async _onRowView(event) {\n this.emit('row:view', event);\n\n // Check for custom handler first\n if (this.options.onItemView) {\n await this.options.onItemView(event.model, event.event);\n return;\n }\n\n const ViewClass = this.getItemViewClass(event.model);\n\n if (ViewClass) {\n // Use custom view class\n const viewInstance = new ViewClass({ model: event.model, collection: this.collection });\n await Dialog.showDialog({\n header: false,\n body: viewInstance,\n size: 'lg',\n centered: false,\n ...this.getFormDialogConfig(this.getModelClass(event.model)),\n ...this.viewDialogOptions\n });\n } else {\n // Fallback to data view\n await Dialog.showData({\n title: `View ${this.getModelName(event.model)} #${event.model.id}`,\n model: event.model\n });\n }\n }\n\n /**\n * Handle row edit action\n */\n async _onRowEdit(event) {\n this.emit('row:edit', event);\n\n // Check for custom handler first\n if (this.options.onItemEdit) {\n await this.options.onItemEdit(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n let formConfig = this.getEditFormConfig(ModelClass);\n\n if (formConfig) {\n if (!formConfig.fields) {\n formConfig = { title: `Edit ${this.getModelName(event.model)}`, fields: formConfig };\n }\n\n const result = await Dialog.showModelForm({\n model: event.model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (!result) return; // Cancelled\n\n if (!result.success || !result?.result?.data.status) {\n Dialog.showError(result?.result?.data?.error || result?.result?.message || \"An error occurred\");\n return;\n }\n\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const result = await Dialog.showDialog({\n title: `Edit ${this.getModelName(event.model)} #${event.model.id}`,\n body: new FormView({\n model: event.model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await event.model.save(result);\n if (!resp.data?.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle row delete action\n */\n async _onRowDelete(event) {\n this.emit('row:delete', event);\n\n // Check for custom handler first\n if (this.options.onItemDelete) {\n await this.options.onItemDelete(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n\n // Get delete template from options, Model class, or use default\n const template = this.deleteTemplate ||\n ModelClass?.DELETE_TEMPLATE ||\n 'Are you sure you want to delete this {{name||\"item\"}}?';\n\n // Render template with model context\n const message = this.renderTemplateString(template, event.model);\n\n const confirmed = await Dialog.confirm({\n message: message || 'Are you sure you want to delete this item?',\n title: 'Confirm Delete',\n confirmText: 'Delete',\n confirmClass: 'btn-danger'\n });\n\n if (confirmed) {\n await event.model.destroy();\n this.collection.fetch();\n }\n }\n\n /**\n * Handle cell edit event\n */\n _onCellEdit(event) {\n this.emit('cell:edit', event);\n }\n\n /**\n * Handle cell save event\n */\n async _onCellSave(event) {\n this.emit('cell:save', event);\n // Model save is now handled directly in TableRow.saveCellEdit()\n }\n\n /**\n * Handle cell cancel event\n */\n _onCellCancel(event) {\n this.emit('cell:cancel', event);\n }\n\n /**\n * Check if fullscreen is supported by the browser\n */\n isFullscreenSupported() {\n return !!(\n document.fullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.msFullscreenEnabled\n );\n }\n\n /**\n * Handle toggle fullscreen action\n */\n async onActionToggleFullscreen(event, element) {\n if (this.isFullscreen) {\n await this.exitFullscreen();\n } else {\n await this.enterFullscreen();\n }\n }\n\n /**\n * Enter fullscreen mode\n */\n async enterFullscreen() {\n try {\n // Use browser's native fullscreen API\n if (this.element.requestFullscreen) {\n await this.element.requestFullscreen();\n } else if (this.element.mozRequestFullScreen) {\n await this.element.mozRequestFullScreen();\n } else if (this.element.webkitRequestFullscreen) {\n await this.element.webkitRequestFullscreen();\n } else if (this.element.msRequestFullscreen) {\n await this.element.msRequestFullscreen();\n }\n\n this.isFullscreen = true;\n this.element.classList.add('table-fullscreen');\n this.updateFullscreenButton();\n\n // Listen for fullscreen change events\n this.setupFullscreenListeners();\n\n this.emit('table:fullscreen:enter');\n\n } catch (error) {\n console.warn('Could not enter fullscreen:', error);\n }\n }\n\n /**\n * Exit fullscreen mode\n */\n async exitFullscreen() {\n try {\n if (document.exitFullscreen) {\n await document.exitFullscreen();\n } else if (document.mozCancelFullScreen) {\n await document.mozCancelFullScreen();\n } else if (document.webkitExitFullscreen) {\n await document.webkitExitFullscreen();\n } else if (document.msExitFullscreen) {\n await document.msExitFullscreen();\n }\n\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n\n this.emit('table:fullscreen:exit');\n\n } catch (error) {\n console.warn('Could not exit fullscreen:', error);\n }\n }\n\n /**\n * Update fullscreen button icon and title\n */\n updateFullscreenButton() {\n const button = this.element?.querySelector('.btn-fullscreen');\n const icon = button?.querySelector('i');\n\n if (button && icon) {\n if (this.isFullscreen) {\n icon.className = 'bi bi-fullscreen-exit';\n button.title = 'Exit Fullscreen';\n } else {\n icon.className = 'bi bi-fullscreen';\n button.title = 'Enter Fullscreen';\n }\n }\n }\n\n /**\n * Setup fullscreen event listeners\n */\n setupFullscreenListeners() {\n // Don't add listeners multiple times\n if (this._fullscreenHandler) return;\n\n const handleFullscreenChange = () => {\n const isCurrentlyFullscreen = !!(\n document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement ||\n document.msFullscreenElement\n );\n\n if (!isCurrentlyFullscreen && this.isFullscreen) {\n // User exited fullscreen via ESC or browser controls\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n this.emit('table:fullscreen:exit');\n }\n };\n\n // Add listeners for all browser prefixes\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n document.addEventListener('msfullscreenchange', handleFullscreenChange);\n\n // Store handler for cleanup\n this._fullscreenHandler = handleFullscreenChange;\n }\n\n /**\n * Cleanup fullscreen listeners\n */\n cleanupFullscreenListeners() {\n if (this._fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this._fullscreenHandler);\n document.removeEventListener('mozfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('webkitfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('msfullscreenchange', this._fullscreenHandler);\n this._fullscreenHandler = null;\n }\n }\n\n /**\n * Override destroy to cleanup fullscreen listeners\n */\n destroy() {\n this.cleanupFullscreenListeners();\n super.destroy();\n }\n\n /**\n * Handle refresh action\n */\n async onActionRefresh(event, element) {\n await this.refresh();\n }\n\n /**\n * Handle add action\n */\n async onActionAdd(event, element) {\n // Check for custom handler first - if provided, just emit event and let handler deal with it\n if (this.options.onAdd) {\n this.emit('table:add', { event });\n await this.options.onAdd(event);\n return;\n }\n\n // Emit event for external listeners\n this.emit('table:add', { event });\n\n const ModelClass = this.getModelClass();\n if (!ModelClass) {\n console.warn('Cannot determine Model class for add operation');\n return;\n }\n\n let formConfig = this.getAddFormConfig(ModelClass);\n\n if (formConfig) {\n const model = new ModelClass();\n if (!formConfig.fields) {\n formConfig = { title: `Add ${this.getModelName()}`, fields: formConfig };\n }\n\n const result = await Dialog.showForm({\n model: model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (result) {\n if (this.options.addRequiresActiveGroup) {\n result.group = this.getApp().activeGroup.id;\n }\n if (this.options.addRequiresActiveUser) {\n result.user = this.getApp().activeUser.id;\n }\n if (this.options.addFormDefaults) {\n Object.assign(result, this.options.addFormDefaults);\n }\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp?.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const model = new ModelClass();\n\n const result = await Dialog.showDialog({\n title: `Add ${this.getModelName()}`,\n body: new FormView({\n model: model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle export action\n */\n async onActionExport(event, element) {\n const format = element.getAttribute('data-format') || 'json';\n\n this.emit('table:export', {\n format: format,\n source: this.exportSource,\n event\n });\n\n if (this.exportSource === 'remote') {\n if (this.collection) {\n await this.collection.download(format);\n } else {\n console.warn('TableView: Cannot export from remote without a collection.');\n }\n } else {\n // Handle local export (future enhancement)\n if (this.options.onExport) {\n await this.options.onExport(this.collection?.toJSON() || [], format);\n } else {\n console.warn('TableView: onExport handler not implemented for local export.');\n }\n }\n }\n\n /**\n * Handle search action (Enter key triggers this via EventDelegate)\n */\n async onActionApplySearch(event, element) {\n const searchTerm = element.value.trim();\n\n if (this.collection) {\n this.setFilter('search', searchTerm);\n\n // Reset to first page when searching\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side filtering\n this.render();\n }\n }\n\n // Update filter pills when search changes\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear search button\n */\n async onActionClearSearch(event, element) {\n // Clear the search filter\n this.setFilter('search', null);\n\n // Reset to first page\n if (this.collection) {\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n }\n\n // Render will rebuild the search input with empty value\n await this.render();\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm: '', event });\n this.emit('params-changed');\n }\n\n /**\n * Get current sort field\n */\n getSortBy() {\n const sort = this.collection?.params?.sort;\n if (!sort) return null;\n return sort.startsWith('-') ? sort.slice(1) : sort;\n }\n\n /**\n * Get current sort direction\n */\n getSortDirection() {\n const sort = this.collection?.params?.sort;\n if (!sort) return 'asc';\n return sort.startsWith('-') ? 'desc' : 'asc';\n }\n\n /**\n * Get sort icon based on current sort direction\n */\n getSortIcon(direction) {\n if (direction === 'asc') {\n return '<i class=\"bi bi-sort-alpha-down text-primary\"></i>';\n } else if (direction === 'desc') {\n return '<i class=\"bi bi-sort-alpha-down-alt text-primary\"></i>';\n } else {\n return '<i class=\"bi bi-three-dots-vertical text-muted\"></i>';\n }\n }\n\n /**\n * Handle sort action\n */\n async onActionSort(event, element) {\n event.preventDefault();\n const field = element.getAttribute('data-field');\n const direction = element.getAttribute('data-direction');\n\n if (this.collection) {\n let newSort;\n\n if (direction === 'none') {\n newSort = undefined; // Remove sort\n } else if (direction === 'desc') {\n newSort = `-${field}`; // Descending sort\n } else {\n newSort = field; // Ascending sort\n }\n\n this.collection.setParams({\n ...this.collection.params,\n sort: newSort,\n start: 0 // Reset to first page when sorting changes\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side sorting\n if (newSort) {\n const desc = newSort.startsWith('-');\n const sortField = desc ? newSort.slice(1) : newSort;\n\n this.collection.sort((a, b) => {\n const aVal = a.get(sortField);\n const bVal = b.get(sortField);\n\n if (aVal < bVal) return desc ? 1 : -1;\n if (aVal > bVal) return desc ? -1 : 1;\n return 0;\n });\n }\n\n this.render();\n }\n }\n\n // Update sort icons in the DOM\n this.updateSortIcons();\n\n this.emit('table:sort', { field, event });\n this.emit('params-changed');\n }\n\n /**\n * Update sort icons in all column headers\n */\n updateSortIcons() {\n if (!this.element) return;\n\n const currentSortField = this.getSortBy();\n const currentSortDir = this.getSortDirection();\n\n // Update all sort dropdown buttons\n this.columns.forEach(column => {\n if (this.sortable && column.sortable !== false) {\n // Parse the column key to get just the field name (without pipes/formatters)\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const dropdown = this.element.querySelector(`[data-bs-toggle=\"dropdown\"][data-column=\"${fieldKey}\"]`);\n if (dropdown) {\n const isSorted = currentSortField === fieldKey;\n const sortIcon = this.getSortIcon(isSorted ? currentSortDir : null);\n dropdown.innerHTML = sortIcon;\n\n // Update dropdown menu items\n const dropdownMenu = dropdown.nextElementSibling;\n if (dropdownMenu) {\n const ascItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"asc\"]`);\n const descItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"desc\"]`);\n const noneItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"none\"]`);\n\n if (ascItem) {\n ascItem.classList.toggle('active', isSorted && currentSortDir === 'asc');\n }\n if (descItem) {\n descItem.classList.toggle('active', isSorted && currentSortDir === 'desc');\n }\n if (noneItem) {\n noneItem.classList.toggle('active', !isSorted || currentSortField !== fieldKey);\n }\n }\n }\n }\n });\n }\n\n /**\n * Handle select all action\n */\n async onActionSelectAll(event, element) {\n event.stopPropagation();\n const isCurrentlyAllSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n\n if (!isCurrentlyAllSelected) {\n // Select all visible items\n this.forEachItem(itemView => {\n if (!itemView.selected) {\n itemView.select();\n }\n });\n } else {\n // Deselect all\n this.clearSelection();\n }\n\n // Update select all checkbox visual state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n selectAllCell.classList.toggle('selected', !isCurrentlyAllSelected);\n }\n\n // Update batch actions panel\n this.updateBatchActionsPanel();\n }\n\n /**\n * Override onBeforeRender to set data properties before rendering\n */\n async onBeforeRender() {\n // Set properties that Mustache needs\n this.searchValue = this.getActiveFilters().search || '';\n this.footerTotals = this.calculateFooterTotals();\n }\n\n /**\n * Override onAfterRender to update pagination info\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Update footer totals in case collection loaded after initial render\n if (this.hasFooterTotals) {\n this.updateFooterTotals();\n }\n\n // Update pagination info\n if (this.paginated && this.collection) {\n const total = this.collection.meta?.count || this.collection.length();\n const start = this.collection.params?.start || 0;\n const size = this.collection.params?.size || 10;\n const end = Math.min(start + size, total);\n\n const startEl = this.element.querySelector('[data-value=\"start\"]');\n const endEl = this.element.querySelector('[data-value=\"end\"]');\n const totalEl = this.element.querySelector('[data-value=\"total\"]');\n\n if (startEl) startEl.textContent = start + 1;\n if (endEl) endEl.textContent = end;\n if (totalEl) totalEl.textContent = total;\n\n // Update page size selector\n const pageSizeSelect = this.element.querySelector('[data-change-action=\"page-size\"]');\n if (pageSizeSelect) {\n pageSizeSelect.value = size;\n }\n\n // Render pagination controls\n this.renderPagination();\n }\n\n // Update sort icons after render\n this.updateSortIcons();\n\n // Update filter pills after render - this is crucial for showing pills on page load\n this.updateFilterPills();\n\n // Re-setup search clear listener after render\n this.setupSearchClearListener();\n }\n\n /**\n * Render pagination controls\n * - Prev/Next wrap around (never disabled)\n * - Truncated page list with first/last and ellipses\n */\n renderPagination() {\n const paginationContainer = this.element.querySelector('[data-container=\"pagination\"]');\n if (!paginationContainer || !this.collection) return;\n\n const total = this.collection.meta?.count || this.collection.length();\n const size = this.collection.params?.size || 10;\n const start = this.collection.params?.start || 0;\n const currentPage = Math.floor(start / size) + 1;\n const totalPages = Math.ceil(total / size);\n\n if (totalPages <= 1) {\n paginationContainer.innerHTML = '';\n return;\n }\n\n const prevPage = currentPage > 1 ? currentPage - 1 : totalPages;\n const nextPage = currentPage < totalPages ? currentPage + 1 : 1;\n\n const pages = [];\n\n // Previous (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${prevPage}\">\n <i class=\"bi bi-chevron-left\"></i>\n </a>\n </li>\n `);\n\n // Build truncated page list: always show 1 and totalPages, with neighbors around current\n const neighbors = 1; // how many pages to show on each side of current\n const visibleSet = new Set([1, totalPages]);\n for (let i = currentPage - neighbors; i <= currentPage + neighbors; i++) {\n if (i >= 1 && i <= totalPages) visibleSet.add(i);\n }\n const visible = Array.from(visibleSet).sort((a, b) => a - b);\n\n // Render pages with ellipses where there are gaps\n let last = 0;\n for (const p of visible) {\n if (last && p - last > 1) {\n // gap -> ellipsis\n pages.push(`\n <li class=\"page-item disabled\"><span class=\"page-link\">…</span></li>\n `);\n }\n pages.push(`\n <li class=\"page-item ${p === currentPage ? 'active' : ''}\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${p}\">${p}</a>\n </li>\n `);\n last = p;\n }\n\n // Next (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${nextPage}\">\n <i class=\"bi bi-chevron-right\"></i>\n </a>\n </li>\n `);\n\n paginationContainer.innerHTML = pages.join('');\n }\n\n /**\n * Handle page change\n * - Normalizes and wraps page number (1..totalPages)\n */\n async onActionPage(event, element) {\n event.preventDefault();\n\n const rawPage = parseInt(element.getAttribute('data-page'), 10);\n const size = this.collection.params?.size || 10;\n const total = this.collection.meta?.count || this.collection.length();\n const totalPages = Math.max(1, Math.ceil(total / size));\n\n let page = isNaN(rawPage) ? 1 : rawPage;\n if (page < 1) page = totalPages;\n if (page > totalPages) page = 1;\n\n this.collection.setParams({\n ...this.collection.params,\n start: (page - 1) * size\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n this.render();\n }\n\n this.emit('table:page', { page, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle page size change\n */\n async onChangePageSize(event, element) {\n const newSize = parseInt(element.value);\n\n if (this.collection) {\n // Reset to first page when changing page size\n this.collection.setParams({\n ...this.collection.params,\n start: 0,\n size: newSize\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n }\n\n this.emit('table:pagesize', { size: newSize, event });\n this.emit('params-changed');\n }\n\n /**\n * Get active filters from collection params\n */\n getActiveFilters() {\n if (!this.collection?.params) {\n return {};\n }\n const { start, size, sort, ...allParams } = this.collection.params;\n const filters = {};\n\n // Reconstruct daterange filters from their component parts\n const processedKeys = new Set();\n\n // First pass: identify and process daterange filters\n const allFilterConfigs = this.getAllAvailableFilters();\n allFilterConfigs.forEach(filterDef => {\n if (filterDef.config.type === 'daterange') {\n const key = filterDef.key;\n const startName = filterDef.config.startName || 'dr_start';\n const endName = filterDef.config.endName || 'dr_end';\n const fieldName = filterDef.config.fieldName || 'dr_field';\n\n // Check if this daterange filter is active for this specific key\n if (allParams[fieldName] === key && (allParams[startName] || allParams[endName])) {\n filters[key] = {\n start: allParams[startName] || '',\n end: allParams[endName] || ''\n };\n\n processedKeys.add(startName);\n processedKeys.add(endName);\n processedKeys.add(fieldName);\n }\n }\n });\n\n // Second pass: add remaining filters\n Object.keys(allParams).forEach(paramKey => {\n if (!processedKeys.has(paramKey)) {\n filters[paramKey] = allParams[paramKey];\n }\n });\n\n // Normalize single-value vs multi-value filters where both exist (e.g., field and field__in)\n Object.keys(filters).forEach(key => {\n if (filters.hasOwnProperty(key)) {\n const inKey = `${key}__in`;\n if (filters.hasOwnProperty(inKey)) {\n // Prefer __in when explicitly provided; remove the base key to avoid duplicate pills\n delete filters[key];\n filters[inKey] = filters[inKey];\n }\n }\n });\n\n return filters;\n }\n\n /**\n * Set a filter value\n */\n setFilter(key, value) {\n if (!this.collection) return;\n\n const filterConfig = this.getFilterConfig(key);\n\n // Handle daterange filters specially\n if (filterConfig && filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n const fieldName = filterConfig.fieldName || 'dr_field';\n\n // Always remove old values first\n delete this.collection.params[startName];\n delete this.collection.params[endName];\n delete this.collection.params[fieldName];\n\n // Set new values if provided and not empty\n if (value && typeof value === 'object' && (value.start || value.end)) {\n if (value.start) this.collection.params[startName] = value.start;\n if (value.end) this.collection.params[endName] = value.end;\n this.collection.params[fieldName] = key;\n }\n } else {\n // Parse key to get field and lookup\n const { field, lookup } = parseFilterKey(key);\n\n // Clear old values - remove both base field and variants\n delete this.collection.params[key];\n delete this.collection.params[field];\n delete this.collection.params[`${field}__in`];\n\n if (!value || (Array.isArray(value) && value.length === 0)) {\n return; // Cleared\n }\n\n // Smart param generation for multiselect fields\n if (Array.isArray(value)) {\n if (value.length === 1) {\n // Single value from array - use simple key (no __in)\n this.collection.params[field] = value[0];\n } else {\n // Multiple values - use __in lookup\n this.collection.params[`${field}__in`] = value.join(',');\n }\n } else {\n // Single value - use key as-is (may include lookup like __gte)\n this.collection.params[key] = value;\n }\n }\n }\n\n /**\n * Get all available filters\n */\n getAllAvailableFilters() {\n const filters = [];\n\n // Add column-based filters\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n filters.push({\n key: fieldKey,\n label: column.filter.label || column.label || fieldKey,\n type: column.filter.type,\n config: column.filter\n });\n }\n });\n\n // Add additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n this.additionalFilters.forEach(filter => {\n filters.push({\n key: filter.name || filter.key,\n label: filter.label,\n type: filter.type,\n config: filter\n });\n });\n }\n\n return filters;\n }\n\n /**\n * Get filter configuration for a key\n */\n getFilterConfig(filterKey) {\n // Check column filters first\n const column = this.columns.find(col => {\n const { fieldKey } = this.parseColumnKey(col.key);\n return fieldKey === filterKey;\n });\n if (column && column.filter) {\n return column.filter;\n }\n\n // Check additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n const filter = this.additionalFilters.find(f => (f.name || f.key) === filterKey);\n if (filter) {\n return filter;\n }\n }\n\n return null;\n }\n\n /**\n * Get filter label\n */\n getFilterLabel(key) {\n if (key === 'search') return 'Search';\n\n const filter = this.filters[key];\n if (filter && filter.label) return filter.label;\n\n const additionalFilter = this.additionalFilters.find(f =>\n (f.name || f.key) === key\n );\n if (additionalFilter && additionalFilter.label) return additionalFilter.label;\n\n return key.charAt(0).toUpperCase() + key.slice(1);\n }\n\n /**\n * Get filter display value\n */\n getFilterDisplayValue(key, value) {\n if (key === 'search') return `\"${value}\"`;\n\n const filter = this.filters[key] ||\n this.additionalFilters.find(f => (f.name || f.key) === key);\n\n if (filter && filter.type === 'daterange' && typeof value === 'object') {\n const start = value.start || '';\n const end = value.end || '';\n return `${start} to ${end}`;\n }\n\n if (filter && filter.type === 'select' && filter.options) {\n if (typeof filter.options[0] === 'object') {\n const option = filter.options.find(opt => opt.value === value);\n return option ? option.label : value;\n }\n return value;\n }\n\n return value;\n }\n\n /**\n * Get icon for filter type\n */\n getFilterIcon(type) {\n const icons = {\n 'text': 'search',\n 'select': 'funnel',\n 'date': 'calendar',\n 'daterange': 'calendar-range',\n 'number': '123',\n 'boolean': 'toggle-on'\n };\n return icons[type] || 'filter';\n }\n\n /**\n * Handle add filter action\n */\n async onActionAddFilter(event, element) {\n const filterKey = element.getAttribute('data-filter-key');\n const filterConfig = this.getFilterConfig(filterKey);\n const currentValue = this.getActiveFilters()[filterKey];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey);\n return;\n }\n\n // Using statically imported Dialog\n\n // Show dialog for this specific filter\n const result = await Dialog.showForm({\n title: `${currentValue !== undefined && currentValue !== '' ? 'Edit' : 'Add'} ${this.getFilterLabel(filterKey)} Filter`,\n size: 'md',\n fields: [this.buildFilterDialogField(filterConfig, currentValue, filterKey)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n // Use the filter key for setFilter (it will handle the lookup internally)\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Build filter dialog field configuration\n */\n buildFilterDialogField(filterConfig, currentValue, filterKey) {\n const field = {\n name: 'filter_value',\n label: filterConfig.label,\n value: currentValue,\n ...filterConfig,\n // Ensure placeholder is passed through (support both casings)\n placeholder: filterConfig.placeholder || filterConfig.placeHolder\n };\n\n // Set current value appropriately based on filter type\n if (filterConfig.type === 'daterange') {\n // Apply defaults for daterange\n field.startName = field.startName || 'dr_start';\n field.endName = field.endName || 'dr_end';\n field.fieldName = field.fieldName || 'dr_field';\n field.format = field.format || 'YYYY-MM-DD';\n field.displayFormat = field.displayFormat || 'MMM DD, YYYY';\n field.separator = field.separator || ' to ';\n field.label = field.label || 'Date Range';\n\n // Handle daterange current values\n if (currentValue && typeof currentValue === 'object') {\n const normalizeDateValue = (val) => {\n if (!val && val !== 0) return '';\n if (val instanceof Date && !isNaN(val)) {\n return val.toISOString().slice(0, 10);\n }\n\n const str = String(val).trim();\n if (!str) return '';\n\n // Numeric timestamps (seconds or milliseconds)\n if (/^-?\\d+$/.test(str)) {\n const num = Number(str);\n const ms = str.length <= 10 ? num * 1000 : num;\n const date = new Date(ms);\n if (!isNaN(date)) {\n return date.toISOString().slice(0, 10);\n }\n }\n\n // ISO or other parseable formats\n const date = new Date(str);\n if (!isNaN(date)) {\n return date.toISOString().slice(0, 10);\n }\n\n // Fallback: return original string\n return str;\n };\n\n field.startDate = normalizeDateValue(currentValue.start || currentValue.from || currentValue.begin || '');\n field.endDate = normalizeDateValue(currentValue.end || currentValue.to || currentValue.finish || '');\n }\n } else if (filterConfig.type === 'multiselect') {\n // Convert comma-separated string to array for multiselect\n let valueArray = [];\n if (currentValue) {\n if (Array.isArray(currentValue)) {\n valueArray = currentValue;\n } else if (typeof currentValue === 'string') {\n // Split by comma and trim whitespace\n valueArray = currentValue.split(',').map(v => v.trim()).filter(v => v);\n }\n }\n\n field.value = valueArray;\n\n // Ensure placeholder is set (support both casings)\n if (!field.placeholder && !field.placeHolder) {\n if (filterConfig.placeholder || filterConfig.placeHolder) {\n field.placeholder = filterConfig.placeholder || filterConfig.placeHolder;\n } else if (filterConfig.label) {\n field.placeholder = `Select ${filterConfig.label}...`;\n }\n }\n }\n\n return field;\n }\n\n /**\n * Extract filter value from form result\n */\n extractFilterValue(filterConfig, formResult) {\n if (filterConfig.type === 'daterange') {\n // Extract start/end values based on naming convention\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n\n const result = {\n start: formResult[startName],\n end: formResult[endName]\n };\n\n return result;\n }\n\n if (filterConfig.type === 'multiselect') {\n // Return array as-is for multiselect\n return formResult.filter_value;\n }\n\n return formResult.filter_value;\n }\n\n /**\n * Apply filters to collection and refresh\n */\n async applyFilters() {\n // Reset to first page when filters change\n if (this.collection) {\n this.collection.params.start = 0;\n }\n\n // For REST collections, fetch data with new filters\n if (this.collection?.restEnabled) {\n try {\n await this.collection.fetch();\n this.render();\n } catch (error) {\n console.error('Failed to fetch filtered data:', error);\n this.render();\n }\n } else {\n this.render();\n }\n\n // Update filter pills display\n this.updateFilterPills();\n\n // Emit params changed event for URL synchronization\n this.emit('params-changed');\n }\n\n /**\n * Handle edit filter action from pill\n */\n async onActionEditFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n\n // Parse the key to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n\n // Try to get filter config using the parsed field name first, then original key\n let filterConfig = this.getFilterConfig(field) || this.getFilterConfig(filterKey);\n\n // Get current value - could be under filterKey or field\n const activeFilters = this.getActiveFilters();\n const currentValue = activeFilters[filterKey] || activeFilters[field];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey, 'or field:', field);\n return;\n }\n\n // Prepare initial form data\n const formData = { filter_value: currentValue };\n if (filterConfig.type === 'daterange' && currentValue && typeof currentValue === 'object') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n formData[startName] = currentValue.start || '';\n formData[endName] = currentValue.end || '';\n }\n\n // Show mini dialog for this specific filter\n const result = await Dialog.showForm({\n title: `Edit ${this.getFilterLabel(field)} Filter`,\n size: 'md',\n data: formData,\n fields: [this.buildFilterDialogField(filterConfig, currentValue, field)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Handle remove filter action\n */\n async onActionRemoveFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n\n // Parse to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n\n // Clear the filter using the original key\n this.setFilter(filterKey, null);\n\n // If removing search filter, clear search inputs\n if (filterKey === 'search') {\n this.updateSearchInputs('');\n }\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after removing\n this.updateFilterPills();\n\n this.emit('filter:remove', { key: filterKey, field });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear all filters action\n */\n async onActionClearAllFilters(event, element) {\n if (!this.collection) return;\n\n // Clear all filters except pagination and sorting\n const { start, size, sort } = this.collection.params;\n this.collection.params = { start, size };\n if (sort) this.collection.params.sort = sort;\n\n // Clear all search inputs\n this.updateSearchInputs('');\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after clearing\n this.updateFilterPills();\n\n this.emit('filters:clear');\n this.emit('params-changed');\n }\n\n /**\n * Update batch actions panel visibility and count\n */\n updateBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) return;\n\n const selectedCount = this.getSelectedItems().length;\n\n if (this.batchBarLocation === 'top') {\n // Handle top panel style\n const panel = this.element?.querySelector('.batch-actions-panel-top');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n\n // Use Bootstrap's d-none class for cleaner show/hide\n if (selectedCount > 0) {\n panel.classList.remove('d-none');\n } else {\n panel.classList.add('d-none');\n }\n }\n } else {\n // Handle bottom panel style (original)\n const panel = this.element?.querySelector('.batch-actions-panel');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n panel.style.display = selectedCount > 0 ? 'block' : 'none';\n }\n }\n\n // Update select all checkbox state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n const allSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n const someSelected = Array.from(this.itemViews.values()).some(item => item.selected);\n\n selectAllCell.classList.toggle('selected', allSelected);\n selectAllCell.classList.toggle('indeterminate', !allSelected && someSelected);\n\n // Update icon for indeterminate state\n const icon = selectAllCell.querySelector('i');\n if (icon) {\n icon.className = !allSelected && someSelected ? 'bi bi-dash' : 'bi bi-check';\n }\n }\n }\n\n /**\n * Handle batch action clicks\n */\n async onActionBatch(event, element) {\n const batchAction = element.getAttribute('data-action').replace('batch-', '');\n const selectedItems = this.getSelectedItems();\n\n this.emit('batch:action', {\n action: batchAction,\n items: selectedItems,\n event\n });\n }\n\n /**\n * Handle clear selection action (for top batch bar)\n */\n async onActionClearSelection(event, element) {\n this.clearSelection();\n this.updateBatchActionsPanel();\n }\n\n /**\n * Handle custom toolbar button with handler function\n */\n async onActionCustomToolbarButton(event, element) {\n const buttonIndex = parseInt(element.getAttribute('data-button-index'), 10);\n const button = this.toolbarButtons[buttonIndex];\n\n if (button && typeof button.handler === 'function') {\n await button.handler.call(this, event, element);\n }\n }\n}\n\nexport default TableView;\n"],"names":["Log","Model","constructor","data","super","endpoint","LogList","Collection","options","ModelClass","size","Member","hasPermission","permission","Array","isArray","some","p","this","permissions","get","fetchForGroup","groupId","fetch","url","MemberList","MemberForms","edit","title","fields","name","type","label","placeholder","columns","EDIT_FORM","ADD_FORM","create","TableRow","ListViewItem","tagName","className","enableTooltips","actions","contextMenu","batchActions","tableView","listView","editingCells","Set","template","buildRowTemplate","getResponsiveClasses","visibility","validBreakpoints","includes","console","warn","join","classes","hide","push","show","isSelectable","forEach","column","columnIndex","combinedClasses","class","editable","filter","c","cellContent","buildCellTemplate","cellAction","action","rowAction","key","buildActionsTemplate","buildContextMenuTemplate","path","formatter","format","length","map","model","id","icon","buildContextMenuItems","menuItem","separator","divider","itemClass","danger","disabled","onAfterRender","cell","element","querySelector","value","context","row","table","index","innerHTML","error","selected","classList","add","setAttribute","onActionEditCell","event","stopPropagation","columnKey","getAttribute","find","col","has","enterEditMode","onActionRowClick","target","closest","emit","onActionView","onActionEdit","onActionDelete","cellElement","contentSpan","currentValue","editor","createCellEditor","originalContent","style","display","editorContainer","document","createElement","appendChild","input","focus","select","dataset","setupEditorEvents","originalValue","editableOptions","createSelectEditor","createSwitchEditor","createTextareaEditor","createTextEditor","inputType","escapeHtml","rows","optionsArray","optionsHtml","option","checked","saveBtn","cancelBtn","addEventListener","e","preventDefault","saveCellEdit","cancelCellEdit","autoSave","newValue","oldValue","save","exitEditMode","setTimeout","remove","displayValue","dataFormatter","pipe","delete","text","div","textContent","addClass","selectCell","deselect","removeClass","LOOKUPS","exact","description","in","not","not_in","gt","gte","lt","lte","contains","icontains","startswith","istartswith","endswith","iendswith","isnull","val","range","parseFilterKey","paramKey","field","lookup","parts","split","possibleLookup","slice","formatFilterDisplay","lookupDef","hasStart","start","hasEnd","end","JSON","stringify","valueStr","String","values","v","trim","formattedValues","DjangoLookups","getLookupDescription","isValidLookup","hasOwnProperty","getAvailableLookups","Object","keys","buildFilterKey","TableView","ListView","selectionMode","selectable","emptyMessage","addButtonIcon","isFullscreen","searchable","sortable","filterable","paginated","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","exportOptions","showExport","exportSource","filters","additionalFilters","hideActivePills","hideActivePillNames","batchBarLocation","addButtonLabel","toolbarButtons","tableOptions","striped","bordered","hover","responsive","searchPlacement","searchPlaceholder","initializeColumns","extractColumnFilters","footerTotalColumns","footer_total","hasFooterTotals","buildTableTemplate","setupCollectionListeners","collection","on","updateFooterTotals","charAt","toUpperCase","parseColumnKey","fieldKey","totals","calculateFooterTotals","totalColumnIndex","safeKey","formatValue","sum","numValue","parseFloat","originalKey","batchPanelTop","buildBatchActionsPanel","batchPanelBottom","buildToolbarTemplate","__fs","fontSize","__val","buildTableClasses","buildTableHeaderTemplate","buildTableFooterTemplate","buildPaginationTemplate","background","buildActionButtonsTemplate","buildFilterDropdownTemplate","buildSearchTemplate","buttons","isFullscreenSupported","showAdd","dropdownItems","opt","button","handler","variant","checkPermissions","iconHtml","labelHtml","dataAttrs","btnClass","buildFilterList","allFilters","getAllAvailableFilters","activeFilters","getActiveFilters","isActive","activeClass","getFilterIcon","config","updateFilterPills","container","pillsHTML","buildActivePills","updateSearchInputs","searchInputs","querySelectorAll","hasSearch","search","toString","filterEntries","entries","getFilterLabel","headerCells","currentSort","getSortBy","getSortDirection","sortIcon","getSortIcon","responsiveClasses","footerCells","actionsHTML","batchPanelTitle","_createItemView","itemTemplate","containerId","itemViews","set","_onItemSelect","updateBatchActionsPanel","_onItemDeselect","_onRowClick","bind","_onRowView","_onRowEdit","_onRowDelete","_onCellEdit","_onCellSave","_onCellCancel","onMounted","setupSearchClearListener","onActionClearSearch","onRowClick","getModelClass","getModelName","MODEL_NAME","replace","getItemViewClass","VIEW_CLASS","getAddFormConfig","getEditFormConfig","getFormDialogConfig","FORM_DIALOG_CONFIG","renderTemplateString","Mustache","render","onItemView","ViewClass","viewInstance","Dialog","showDialog","header","body","centered","showData","onItemEdit","formConfig","result","showModelForm","success","status","showError","message","FormView","formFields","resp","refresh","onItemDelete","DELETE_TEMPLATE","confirm","confirmText","confirmClass","destroy","fullscreenEnabled","mozFullScreenEnabled","webkitFullscreenEnabled","msFullscreenEnabled","onActionToggleFullscreen","exitFullscreen","enterFullscreen","requestFullscreen","mozRequestFullScreen","webkitRequestFullscreen","msRequestFullscreen","updateFullscreenButton","setupFullscreenListeners","mozCancelFullScreen","webkitExitFullscreen","msExitFullscreen","_fullscreenHandler","handleFullscreenChange","fullscreenElement","mozFullScreenElement","webkitFullscreenElement","msFullscreenElement","cleanupFullscreenListeners","removeEventListener","onActionRefresh","onActionAdd","onAdd","showForm","addRequiresActiveGroup","group","getApp","activeGroup","addRequiresActiveUser","user","activeUser","addFormDefaults","assign","onActionExport","source","download","onExport","toJSON","onActionApplySearch","searchTerm","setFilter","params","restEnabled","sort","startsWith","direction","onActionSort","newSort","setParams","desc","sortField","a","b","aVal","bVal","updateSortIcons","currentSortField","currentSortDir","dropdown","isSorted","dropdownMenu","nextElementSibling","ascItem","descItem","noneItem","toggle","onActionSelectAll","isCurrentlyAllSelected","from","every","item","clearSelection","forEachItem","selectAllCell","onBeforeRender","searchValue","footerTotals","total","meta","count","Math","min","startEl","endEl","totalEl","pageSizeSelect","renderPagination","paginationContainer","currentPage","floor","totalPages","ceil","prevPage","nextPage","pages","visibleSet","i","visible","last","onActionPage","rawPage","parseInt","max","page","isNaN","onChangePageSize","newSize","allParams","processedKeys","filterDef","startName","endName","fieldName","inKey","filterConfig","getFilterConfig","filterKey","f","additionalFilter","getFilterDisplayValue","date","daterange","number","boolean","onActionAddFilter","buildFilterDialogField","newFilterValue","extractFilterValue","applyFilters","placeHolder","displayFormat","normalizeDateValue","Date","toISOString","str","test","num","Number","ms","startDate","begin","endDate","to","finish","valueArray","formResult","filter_value","onActionEditFilter","formData","onActionRemoveFilter","onActionClearAllFilters","selectedCount","getSelectedItems","panel","countEl","allSelected","someSelected","onActionBatch","batchAction","selectedItems","items","onActionClearSelection","onActionCustomToolbarButton","buttonIndex","call"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TableView-DemRVhnX.js","sources":["../../src/core/models/Log.js","../../src/core/models/Member.js","../../src/core/views/table/TableRow.js","../../src/core/utils/DjangoLookups.js","../../src/core/views/table/TableView.js"],"sourcesContent":["\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Log extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/logs',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass LogList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Log,\n endpoint: '/api/logs',\n size: 10,\n ...options,\n });\n }\n}\n\nexport { Log, LogList };\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Member extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group/member',\n });\n }\n\n hasPermission(permission) {\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n return permissions[permission] == true;\n }\n\n async fetchForGroup(groupId) {\n return await this.fetch({ url: `/api/group/${groupId}/member` });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass MemberList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Member,\n endpoint: '/api/group/member',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst MemberForms = {\n\n edit: {\n title: 'Edit Membership',\n fields: [\n {\n name: 'user.display_name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name'\n },\n {\n name: 'metadata.role',\n type: 'text',\n label: 'Role',\n placeholder: 'Enter role'\n },\n {\n name: `is_active`,\n type: 'switch',\n label: \"Is Enabled\",\n columns: 12\n }\n ]\n }\n};\n\n\nMember.EDIT_FORM = MemberForms.edit;\nMember.ADD_FORM = MemberForms.create;\n\n\nexport { Member, MemberList, MemberForms };\n","/**\n * TableRow - Individual row view for TableView\n *\n * Extends ListViewItem to render table rows with proper cell formatting\n * and support for all table features like selection, actions, and context menus.\n *\n * @example\n * const row = new TableRow({\n * model: userModel,\n * columns: tableColumns,\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport ListViewItem from '../list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TableRow extends ListViewItem {\n constructor(options = {}) {\n super({\n tagName: 'tr',\n className: 'table-row',\n enableTooltips: true,\n ...options\n });\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.tableView = options.tableView || options.listView || null;\n\n // Inline editing state\n this.editingCells = new Set(); // Track which cells are being edited\n\n // Override template to generate table cells\n this.template = this.buildRowTemplate();\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Build the row template with table cells\n */\n buildRowTemplate() {\n let template = '';\n\n // Selection checkbox cell\n if (this.tableView && this.tableView.isSelectable()) {\n template += `\n <td style=\"padding: 0;\">\n <div class=\"mojo-select-cell {{#selected}}selected{{/selected}}\"\n data-action=\"select\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </td>\n `;\n }\n\n // Data cells for each column\n this.columns.forEach((column, columnIndex) => {\n const cellClass = column.class || column.className || '';\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n const editableClass = column.editable ? 'editable-cell' : '';\n const combinedClasses = [cellClass, responsiveClasses, editableClass].filter(c => c).join(' ');\n const cellContent = this.buildCellTemplate(column, columnIndex);\n\n // Determine cell action\n let cellAction = column.action;\n if (!cellAction && column.editable) {\n cellAction = 'edit-cell';\n } else if (!cellAction && this.tableView.rowAction) {\n cellAction = this.tableView.rowAction;\n }\n\n if (cellAction) {\n template += `<td class=\"${combinedClasses}\" data-action=\"${cellAction}\" data-column=\"${column.key}\">${cellContent}</td>`;\n } else {\n template += `<td class=\"${combinedClasses}\" data-column=\"${column.key}\">${cellContent}</td>`;\n }\n });\n\n // Actions cell\n if (this.actions) {\n template += this.buildActionsTemplate();\n } else if (this.contextMenu) {\n template += this.buildContextMenuTemplate();\n }\n\n return template;\n }\n\n /**\n * Build template for a single cell\n */\n /**\n * Build template for a single cell\n */\n buildCellTemplate(column, columnIndex = 0) {\n // Build path for Mustache to access the value\n const path = `model.${column.key}`;\n // Support both 'formatter' and 'format' for consistency with DataView\n const formatter = column.formatter || column.format;\n if (formatter) {\n // For string formatters that are pipe expressions\n if (typeof formatter === 'string') {\n return `{{{${path}|${formatter}}}}`;\n } else if (typeof formatter === 'function') {\n // Keep legacy data-formatter key selector for compatibility, but\n // use a per-column id so duplicate keys can be formatted correctly.\n return `<span data-formatter=\"${column.key}\" data-formatter-id=\"${columnIndex}\">{{${path}}}</span>`;\n }\n }\n\n if (column.template) {\n return column.template;\n }\n\n // For editable cells, wrap content in a span for easy replacement\n if (column.editable) {\n return `<span class=\"cell-content\" data-field=\"${column.key}\">{{{${path}}}}</span>`;\n }\n\n return `{{{${path}}}}`;\n }\n\n /**\n * Build actions cell template\n */\n buildActionsTemplate() {\n if (!this.actions || this.actions.length === 0) return '';\n\n const buttons = this.actions.map(action => {\n if (typeof action === 'string') {\n switch (action) {\n case 'view':\n return `\n <button class=\"btn btn-sm btn-outline-primary\"\n data-action=\"view\"\n title=\"View\">\n <i class=\"bi bi-eye\"></i>\n </button>\n `;\n\n case 'edit':\n return `\n <button class=\"btn btn-sm btn-outline-secondary\"\n data-action=\"edit\"\n title=\"Edit\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n `;\n\n case 'delete':\n return `\n <button class=\"btn btn-sm btn-outline-danger\"\n data-action=\"delete\"\n title=\"Delete\">\n <i class=\"bi bi-trash\"></i>\n </button>\n `;\n\n default:\n return '';\n }\n } else if (typeof action === 'object') {\n return `\n <button class=\"btn btn-sm ${action.class || 'btn-outline-primary'}\"\n data-id=\"${this.model.id}\"\n data-action=\"${action.action}\"\n title=\"${action.label || ''}\">\n ${action.icon ? `<i class=\"${action.icon}\"></i>` : ''}\n ${action.label && !action.icon ? action.label : ''}\n </button>\n `;\n }\n return '';\n }).join('');\n\n return `<td><div class=\"btn-group btn-group-sm\">${buttons}</div></td>`;\n }\n\n /**\n * Build context menu cell template\n */\n buildContextMenuTemplate() {\n if (!this.contextMenu || this.contextMenu.length === 0) return '';\n\n return `\n <td class=\"text-end\" style=\"width: 1px;\">\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-link border-0\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n style=\"color: #6c757d;\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end shadow-sm\">\n ${this.buildContextMenuItems()}\n </ul>\n </div>\n </td>\n `;\n }\n\n /**\n * Build context menu items\n */\n buildContextMenuItems() {\n return this.contextMenu.map(menuItem => {\n if (menuItem.separator||menuItem.divider) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n let itemClass = 'dropdown-item';\n if (menuItem.action === 'delete' || menuItem.danger) {\n itemClass += ' text-danger';\n }\n if (menuItem.disabled) {\n itemClass += ' disabled';\n }\n\n return `\n <li>\n <a class=\"${itemClass}\" href=\"#\"\n data-id=\"{{model.id}}\"\n data-action=\"${menuItem.action}\"\n ${menuItem.disabled ? 'aria-disabled=\"true\" tabindex=\"-1\"' : ''}>\n ${menuItem.icon ? `<i class=\"${menuItem.icon} me-2\"></i>` : ''}\n ${menuItem.label}\n </a>\n </li>\n `;\n }).join('');\n }\n\n /**\n * Override onAfterRender to apply function formatters and templates\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Apply function formatters\n this.columns.forEach((column, columnIndex) => {\n if (column.formatter && typeof column.formatter === 'function') {\n let cell = this.element.querySelector(`[data-formatter-id=\"${columnIndex}\"]`);\n if (!cell) {\n // Backward-compatible fallback for existing markup/selectors.\n cell = this.element.querySelector(`[data-formatter=\"${column.key}\"]`);\n }\n if (cell) {\n const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n const context = {\n value,\n row: this.model, // deprecate this\n model: this.model,\n column,\n table: this.tableView,\n index: this.index\n };\n try {\n cell.innerHTML = column.formatter(value, context);\n } catch (error) {\n console.error(`Error formatting cell for column ${column.key}:`, error);\n }\n }\n }\n\n // Apply function templates\n // if (column.template && typeof column.template === 'function') {\n // const cell = this.element.querySelector(`[data-template=\"${column.key}\"]`);\n // if (cell) {\n // const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n // cell.innerHTML = column.template(value, this.model);\n // }\n // }\n });\n\n // Update selection state\n if (this.selected) {\n this.element.classList.add('selected');\n }\n\n // Set data-id attribute for easy identification\n const id = this.model.get ? this.model.get('id') : this.model.id;\n if (id) {\n this.element.setAttribute('data-id', id);\n }\n }\n\n /**\n * Handle edit cell action\n */\n async onActionEditCell(event, element) {\n event.stopPropagation();\n\n const columnKey = element.getAttribute('data-column');\n const column = this.columns.find(col => col.key === columnKey);\n\n if (!column || !column.editable) return;\n\n // Don't enter edit mode if already editing this cell\n if (this.editingCells.has(columnKey)) return;\n\n await this.enterEditMode(columnKey, column, element);\n }\n\n /**\n * Handle row click action\n */\n async onActionRowClick(event, element) {\n // Don't trigger row click if clicking on action buttons or editing\n if (event.target.closest('.btn-group') || event.target.closest('.dropdown') || event.target.closest('.cell-editor')) {\n return;\n }\n\n // Emit row click event\n this.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n\n // Notify parent TableView\n if (this.tableView) {\n this.tableView.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n }\n }\n\n /**\n * Handle view action\n */\n async onActionView(event, element) {\n event.stopPropagation();\n\n this.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Handle edit action\n */\n async onActionEdit(event, element) {\n event.stopPropagation();\n\n this.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n }\n return true;\n }\n\n /**\n * Handle delete action\n */\n async onActionDelete(event, element) {\n event.stopPropagation();\n\n this.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Enter edit mode for a cell\n */\n async enterEditMode(columnKey, column, cellElement) {\n const contentSpan = cellElement.querySelector('.cell-content');\n if (!contentSpan) return;\n\n this.editingCells.add(columnKey);\n const currentValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Create editor based on column configuration\n const editor = this.createCellEditor(column, currentValue);\n\n // Replace content with editor\n const originalContent = contentSpan.innerHTML;\n contentSpan.style.display = 'none';\n\n const editorContainer = document.createElement('div');\n editorContainer.className = 'cell-editor';\n editorContainer.innerHTML = editor;\n cellElement.appendChild(editorContainer);\n\n // Focus the input\n const input = editorContainer.querySelector('input, select, .form-check-input');\n if (input) {\n input.focus();\n if (input.type === 'text' || input.type === 'textarea') {\n input.select();\n }\n }\n\n // Store original content for cancel\n editorContainer.dataset.originalContent = originalContent;\n editorContainer.dataset.columnKey = columnKey;\n\n // Set up event listeners\n this.setupEditorEvents(editorContainer, columnKey, column);\n\n this.emit('cell:edit', {\n row: this,\n model: this.model,\n column: columnKey,\n originalValue: currentValue\n });\n }\n\n /**\n * Create cell editor HTML based on column configuration\n */\n createCellEditor(column, currentValue) {\n const options = column.editableOptions || {};\n\n switch (options.type) {\n case 'select':\n return this.createSelectEditor(options, currentValue);\n case 'switch':\n case 'checkbox':\n return this.createSwitchEditor(options, currentValue);\n case 'textarea':\n return this.createTextareaEditor(options, currentValue);\n default:\n return this.createTextEditor(options, currentValue);\n }\n }\n\n /**\n * Create text input editor\n */\n createTextEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const inputType = options.inputType || 'text';\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <input type=\"${inputType}\"\n class=\"form-control form-control-sm cell-input\"\n value=\"${this.escapeHtml(currentValue || '')}\"\n placeholder=\"${placeholder}\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create textarea editor\n */\n createTextareaEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const rows = options.rows || 2;\n\n return `\n <div class=\"d-flex gap-1\">\n <textarea class=\"form-control form-control-sm cell-input\"\n rows=\"${rows}\"\n placeholder=\"${placeholder}\">${this.escapeHtml(currentValue || '')}</textarea>\n <div class=\"d-flex flex-column gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Create select dropdown editor\n */\n createSelectEditor(options, currentValue) {\n const optionsArray = options.options || [];\n let optionsHtml = '';\n\n optionsArray.forEach(option => {\n if (typeof option === 'string') {\n const selected = option === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option}\" ${selected}>${option}</option>`;\n } else if (typeof option === 'object' && option.value !== undefined) {\n const selected = option.value === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option.value}\" ${selected}>${option.label || option.value}</option>`;\n }\n });\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm cell-input\">\n ${optionsHtml}\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create switch/checkbox editor\n */\n createSwitchEditor(options, currentValue) {\n const checked = currentValue ? 'checked' : '';\n const switchType = options.type === 'switch' ? 'form-switch' : '';\n\n return `\n <div class=\"d-flex gap-2 align-items-center\">\n <div class=\"form-check ${switchType}\">\n <input class=\"form-check-input cell-input\" type=\"checkbox\" ${checked}>\n </div>\n <div class=\"d-flex gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Setup event listeners for cell editor\n */\n setupEditorEvents(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n const saveBtn = editorContainer.querySelector('.cell-save');\n const cancelBtn = editorContainer.querySelector('.cell-cancel');\n\n // Save on Enter (for text inputs)\n if (input && (input.type === 'text' || input.type === 'email' || input.type === 'number')) {\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.saveCellEdit(editorContainer, columnKey, column);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.cancelCellEdit(editorContainer, columnKey);\n }\n });\n }\n\n // Save on change for selects and checkboxes (if auto-save enabled)\n if (input && (input.type === 'checkbox' || input.tagName === 'SELECT') && column.autoSave !== false) {\n input.addEventListener('change', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n }\n\n // Button events\n saveBtn?.addEventListener('click', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n\n cancelBtn?.addEventListener('click', () => {\n this.cancelCellEdit(editorContainer, columnKey);\n });\n }\n\n /**\n * Save cell edit\n */\n async saveCellEdit(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n if (!input) return;\n\n let newValue;\n\n // Extract value based on input type\n if (input.type === 'checkbox') {\n newValue = input.checked;\n } else if (input.tagName === 'SELECT') {\n newValue = input.value;\n } else {\n newValue = input.value;\n }\n\n const oldValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Save to model and backend\n try {\n if (this.model.save) {\n await this.model.save({ [columnKey]: newValue });\n } else {\n // Fallback for models without save method\n this.model[columnKey] = newValue;\n }\n\n // Exit edit mode\n this.exitEditMode(editorContainer, columnKey, newValue);\n\n // Emit save event\n this.emit('cell:save', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue\n });\n\n } catch (error) {\n // Show error and keep in edit mode\n console.error('Failed to save cell edit:', error);\n this.emit('cell:save:error', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue,\n error: error\n });\n\n // Could show an error message in the UI\n editorContainer.classList.add('saving-error');\n setTimeout(() => editorContainer.classList.remove('saving-error'), 3000);\n }\n }\n\n /**\n * Cancel cell edit\n */\n cancelCellEdit(editorContainer, columnKey) {\n const originalContent = editorContainer.dataset.originalContent;\n this.exitEditMode(editorContainer, columnKey, null, originalContent);\n\n this.emit('cell:cancel', {\n row: this,\n model: this.model,\n column: columnKey\n });\n }\n\n /**\n * Exit edit mode and restore content\n */\n exitEditMode(editorContainer, columnKey, newValue = null, originalContent = null) {\n const cellElement = editorContainer.closest('td');\n const contentSpan = cellElement.querySelector('.cell-content');\n\n if (contentSpan) {\n if (newValue !== null) {\n // Update display with new value (with proper formatting if needed)\n const column = this.columns.find(col => col.key === columnKey);\n let displayValue = newValue;\n\n if (column && column.formatter && typeof column.formatter === 'string') {\n displayValue = dataFormatter.pipe(newValue, column.formatter);\n }\n\n contentSpan.innerHTML = this.escapeHtml(displayValue);\n } else if (originalContent) {\n // Restore original content on cancel\n contentSpan.innerHTML = originalContent;\n }\n\n contentSpan.style.display = '';\n }\n\n // Remove editor\n editorContainer.remove();\n this.editingCells.delete(columnKey);\n }\n\n /**\n * Escape HTML for safe display\n */\n escapeHtml(text) {\n if (text === null || text === undefined) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Override select to handle table-specific selection UI\n */\n select() {\n super.select();\n this.addClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.add('selected');\n }\n }\n\n /**\n * Override deselect to handle table-specific selection UI\n */\n deselect() {\n super.deselect();\n this.removeClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.remove('selected');\n }\n }\n}\n\nexport default TableRow;\n","/**\n * DjangoLookups - Utility for Django-style filter lookup parsing and formatting\n * \n * Provides utilities to parse filter keys like \"status__in\" or \"created__gte\"\n * and format them into human-readable display text for filter pills.\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * formatFilterDisplay('status__in', 'new,open', 'Status') // \"Status in 'new', 'open'\"\n */\n\n/**\n * Supported Django-style lookups with display configurations\n * Only includes commonly used lookups (KISS principle)\n */\nexport const LOOKUPS = {\n // Comparison\n 'exact': { \n display: 'is',\n description: 'Exact match'\n },\n 'in': { \n display: 'in',\n description: 'Match any of the values (comma-separated)'\n },\n 'not': { \n display: 'is not',\n description: 'Does not match'\n },\n 'not_in': { \n display: 'not in',\n description: 'Does not match any of the values'\n },\n 'gt': { \n display: '>',\n description: 'Greater than'\n },\n 'gte': { \n display: '>=',\n description: 'Greater than or equal to'\n },\n 'lt': { \n display: '<',\n description: 'Less than'\n },\n 'lte': { \n display: '<=',\n description: 'Less than or equal to'\n },\n \n // String operations\n 'contains': { \n display: 'contains',\n description: 'Contains substring (case-sensitive)'\n },\n 'icontains': { \n display: 'contains',\n description: 'Contains substring (case-insensitive)'\n },\n 'startswith': { \n display: 'starts with',\n description: 'Starts with substring (case-sensitive)'\n },\n 'istartswith': { \n display: 'starts with',\n description: 'Starts with substring (case-insensitive)'\n },\n 'endswith': { \n display: 'ends with',\n description: 'Ends with substring (case-sensitive)'\n },\n 'iendswith': { \n display: 'ends with',\n description: 'Ends with substring (case-insensitive)'\n },\n \n // Null checks\n 'isnull': { \n display: (val) => val === 'true' || val === true ? 'is null' : 'is not null',\n description: 'Check if value is null or not'\n },\n \n // Range operations\n 'range': { \n display: 'between',\n description: 'Between two values (comma-separated)'\n }\n};\n\n/**\n * Parse a filter key into field name and lookup operator\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\", \"created__gte\")\n * @returns {Object} Object with field and lookup properties\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * parseFilterKey('status') // { field: 'status', lookup: null }\n * parseFilterKey('user__profile__name__icontains') // { field: 'user__profile__name', lookup: 'icontains' }\n */\nexport function parseFilterKey(paramKey) {\n if (!paramKey || typeof paramKey !== 'string') {\n return { field: paramKey, lookup: null };\n }\n\n const parts = paramKey.split('__');\n \n // Single part, no lookup\n if (parts.length === 1) {\n return { field: paramKey, lookup: null };\n }\n \n // Check if last part is a valid lookup\n const possibleLookup = parts[parts.length - 1];\n if (LOOKUPS[possibleLookup]) {\n return { \n field: parts.slice(0, -1).join('__'), \n lookup: possibleLookup \n };\n }\n \n // No valid lookup found, treat entire string as field name\n return { field: paramKey, lookup: null };\n}\n\n/**\n * Format a filter key and value into human-readable display text\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\")\n * @param {string|Array} value - Filter value(s)\n * @param {string} label - Human-readable field label\n * @returns {string} Formatted display text\n * \n * @example\n * formatFilterDisplay('status__in', 'new,open', 'Status') \n * // \"Status in 'new', 'open'\"\n * \n * formatFilterDisplay('created__gte', '2025-01-01', 'Created') \n * // \"Created >= '2025-01-01'\"\n * \n * formatFilterDisplay('name__icontains', 'john', 'Name') \n * // \"Name contains 'john'\"\n */\nexport function formatFilterDisplay(paramKey, value, label) {\n if (!paramKey || value === null || value === undefined) {\n return '';\n }\n\n const { field, lookup } = parseFilterKey(paramKey);\n const lookupDef = LOOKUPS[lookup];\n \n // Handle object-based values (e.g., daterange payloads)\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const hasStart = value.start !== undefined && value.start !== null && value.start !== '';\n const hasEnd = value.end !== undefined && value.end !== null && value.end !== '';\n\n if (hasStart || hasEnd) {\n if (hasStart && hasEnd) {\n return `${label} between '${value.start}' and '${value.end}'`;\n }\n if (hasStart) {\n return `${label} from '${value.start}'`;\n }\n return `${label} until '${value.end}'`;\n }\n\n // Fallback to JSON if it's some other object shape\n return `${label} is '${JSON.stringify(value)}'`;\n }\n\n // Convert array to comma-separated string if needed\n const valueStr = Array.isArray(value) ? value.join(',') : String(value);\n \n // No lookup or exact lookup - simple \"is\" format\n if (!lookup || lookup === 'exact') {\n return `${label} is '${valueStr}'`;\n }\n \n // Multi-value lookups (in, not_in)\n if (lookup === 'in' || lookup === 'not_in') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 0) {\n return `${label} ${lookupDef.display}`;\n }\n const formattedValues = values.map(v => `'${v}'`).join(', ');\n return `${label} ${lookupDef.display} ${formattedValues}`;\n }\n \n // Range lookup - special formatting\n if (lookup === 'range') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 2) {\n return `${label} between '${values[0]}' and '${values[1]}'`;\n }\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Null check - dynamic display based on value\n if (lookup === 'isnull') {\n const displayText = typeof lookupDef.display === 'function' \n ? lookupDef.display(valueStr) \n : lookupDef.display;\n return `${label} ${displayText}`;\n }\n \n // Standard lookup with operator\n if (lookupDef) {\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Fallback for unknown lookups\n return `${label} is '${valueStr}'`;\n}\n\n/**\n * Get a user-friendly description of a lookup operator\n * \n * @param {string} lookup - Lookup operator (e.g., \"in\", \"gte\", \"icontains\")\n * @returns {string} Human-readable description\n * \n * @example\n * getLookupDescription('in') // \"Match any of the values (comma-separated)\"\n * getLookupDescription('gte') // \"Greater than or equal to\"\n */\nexport function getLookupDescription(lookup) {\n const lookupDef = LOOKUPS[lookup];\n return lookupDef ? lookupDef.description : 'Exact match';\n}\n\n/**\n * Check if a string is a valid lookup operator\n * \n * @param {string} lookup - Potential lookup operator\n * @returns {boolean} True if valid lookup\n * \n * @example\n * isValidLookup('in') // true\n * isValidLookup('foo') // false\n */\nexport function isValidLookup(lookup) {\n return lookup && LOOKUPS.hasOwnProperty(lookup);\n}\n\n/**\n * Get all available lookup operators\n * \n * @returns {Array<string>} Array of lookup operator names\n * \n * @example\n * getAvailableLookups() // ['exact', 'in', 'not', 'not_in', 'gt', ...]\n */\nexport function getAvailableLookups() {\n return Object.keys(LOOKUPS);\n}\n\n/**\n * Build a filter key from field name and lookup operator\n * \n * @param {string} field - Field name\n * @param {string} lookup - Lookup operator (optional)\n * @returns {string} Combined filter key\n * \n * @example\n * buildFilterKey('status', 'in') // \"status__in\"\n * buildFilterKey('status') // \"status\"\n */\nexport function buildFilterKey(field, lookup = null) {\n if (!field) return '';\n if (!lookup) return field;\n return `${field}__${lookup}`;\n}\n\nexport default {\n LOOKUPS,\n parseFilterKey,\n formatFilterDisplay,\n getLookupDescription,\n isValidLookup,\n getAvailableLookups,\n buildFilterKey\n};\n","/**\n * TableView - Advanced data table component extending ListView\n *\n * Leverages ListView's view management system for efficient row rendering.\n * Each row is a separate TableRow view that only re-renders when its model changes.\n *\n * @example\n * const table = new TableView({\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email', visibility: 'md' }, // Hidden on xs/sm, visible md+\n * { key: 'phone', label: 'Phone', visibility: 'lg' }, // Visible only on lg+\n * { key: 'created', label: 'Created', formatter: 'date', visibility: 'xl' } // Visible only on xl+\n * ],\n * actions: ['view', 'edit', 'delete'],\n * selectionMode: 'multiple'\n * });\n */\n\nimport ListView from '../list/ListView.js';\nimport TableRow from './TableRow.js';\nimport Mustache from '@core/utils/mustache.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport FormView from '@core/forms/FormView.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\nimport { parseFilterKey, formatFilterDisplay } from '@core/utils/DjangoLookups.js';\n\nclass TableView extends ListView {\n constructor(options = {}) {\n // Set up table-specific defaults before calling super\n const tableOptions = {\n className: 'table-view-component',\n itemClass: options.itemClass || TableRow,\n selectionMode: options.selectable ? 'multiple' : 'none',\n emptyMessage: options.emptyMessage || 'No data available',\n addButtonIcon: options.addButtonIcon || 'bi bi-plus-circle',\n ...options\n };\n\n super(tableOptions);\n\n // Fullscreen state\n this.isFullscreen = false;\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.searchable = options.searchable !== false;\n this.sortable = options.sortable !== false;\n this.filterable = options.filterable !== false;\n this.paginated = options.paginated !== false;\n this.clickAction = options.clickAction || \"view\";\n\n // Model operation configurations\n this.itemView = options.itemView;\n this.addForm = options.addForm;\n this.editForm = options.editForm;\n this.deleteTemplate = options.deleteTemplate;\n this.formDialogConfig = options.formDialogConfig || {};\n this.viewDialogOptions = options.viewDialogOptions || {};\n\n // Export configuration\n this.exportOptions = options.exportOptions || null;\n if (this.options.showExport && !this.exportOptions) {\n this.exportOptions = [\n { format: 'csv', label: 'Export as CSV', icon: 'bi bi-file-earmark-spreadsheet' },\n { format: 'json', label: 'Export as JSON', icon: 'bi bi-file-earmark-code' }\n ];\n }\n this.exportSource = options.exportSource || 'remote';\n\n // Filter configuration\n this.filters = {};\n this.additionalFilters = options.filters || [];\n this.hideActivePills = options.hideActivePills || false;\n this.hideActivePillNames = options.hideActivePillNames || [];\n this.rowAction = options.rowAction || \"row-click\";\n this.batchBarLocation = options.batchBarLocation || \"bottom\"; // \"top\" or \"bottom\"\n\n this.options.addButtonLabel = options.addButtonLabel || 'Add';\n\n // Custom toolbar buttons\n this.toolbarButtons = options.toolbarButtons || [];\n\n // Table display options\n this.tableOptions = {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n size: null, // null, 'sm', 'lg'\n ...options.tableOptions\n };\n\n // Search configuration\n this.searchPlacement = options.searchPlacement || 'toolbar'; // 'toolbar' or 'dropdown'\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n\n // Initialize column configuration BEFORE building template\n this.initializeColumns();\n\n // Extract filters from columns BEFORE building template\n this.extractColumnFilters();\n\n // Detect columns that need footer totals\n this.footerTotalColumns = this.columns.filter(col => col.footer_total === true);\n this.hasFooterTotals = this.footerTotalColumns.length > 0;\n\n // Build template with Mustache variables\n this.template = this.buildTableTemplate();\n\n // Listen for collection changes to update totals\n this.setupCollectionListeners();\n }\n\n /**\n * Setup collection event listeners for totals updates\n */\n setupCollectionListeners() {\n if (this.hasFooterTotals && this.collection) {\n // Re-render totals when collection data changes\n this.collection.on('reset add remove change', () => {\n this.updateFooterTotals();\n });\n }\n }\n\n /**\n * Initialize column configuration\n */\n initializeColumns() {\n this.columns.forEach(column => {\n // Ensure each column has a key\n if (!column.key && column.name) {\n column.key = column.name;\n }\n\n // Set default label if not provided\n if (!column.label && !column.title) {\n column.label = column.key.charAt(0).toUpperCase() + column.key.slice(1);\n }\n });\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Extract column key and formatter from combined key (e.g., \"sales_amount|currency\")\n */\n parseColumnKey(key) {\n const parts = key.split('|');\n return {\n fieldKey: parts[0],\n formatter: parts[1] || null\n };\n }\n\n /**\n * Update footer totals in the DOM without full re-render\n */\n updateFooterTotals() {\n if (!this.hasFooterTotals || !this.element) return;\n\n const totals = this.calculateFooterTotals();\n console.log('Updating footer totals in DOM:', totals);\n\n // Update each total cell in the footer\n let totalColumnIndex = 0;\n this.columns.forEach((column) => {\n if (column.footer_total) {\n const safeKey = `col_${totalColumnIndex}`;\n const cell = this.element.querySelector(`[data-total-column=\"${safeKey}\"]`);\n\n if (cell && totals[safeKey]) {\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n let displayValue;\n\n if (formatter && typeof formatter === 'string') {\n // Use DataFormatter if available\n displayValue = this.formatValue(totals[safeKey].value, formatter);\n } else {\n displayValue = totals[safeKey].value;\n }\n\n cell.textContent = displayValue;\n }\n totalColumnIndex++;\n }\n });\n }\n\n /**\n * Format a value using DataFormatter\n */\n formatValue(value, formatter) {\n try {\n return dataFormatter.pipe(value, formatter);\n } catch (e) {\n console.warn('Error formatting value:', e);\n return value;\n }\n }\n\n /**\n * Calculate totals for footer columns\n */\n calculateFooterTotals() {\n if (!this.hasFooterTotals || !this.collection || this.collection.length === 0) {\n return {};\n }\n\n const totals = {};\n\n this.footerTotalColumns.forEach((column, totalColumnIndex) => {\n const { fieldKey, formatter } = this.parseColumnKey(column.key);\n let sum = 0;\n\n // Sum values from all items in collection\n this.collection.forEach(model => {\n const value = model.get ? model.get(fieldKey) : model[fieldKey];\n const numValue = parseFloat(value) || 0;\n sum += numValue;\n });\n\n // Debug logging\n console.log(`Footer total for ${column.key}: ${sum} (from ${this.collection.length} items)`);\n\n // Use safe key for Mustache (avoid special characters)\n const safeKey = `col_${totalColumnIndex}`;\n\n // Store total with formatter info\n totals[safeKey] = {\n value: sum,\n formatter: formatter || column.formatter,\n fieldKey: fieldKey,\n originalKey: column.key\n };\n });\n\n return totals;\n }\n\n /**\n * Extract filters from column configuration\n */\n extractColumnFilters() {\n this.filters = {};\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n this.filters[fieldKey] = column.filter;\n }\n });\n }\n\n isSelectable() {\n return this.batchActions && this.batchActions.length > 0 && this.selectionMode == 'multiple';\n }\n\n /**\n * Build the complete table template\n */\n buildTableTemplate() {\n const batchPanelTop = this.batchBarLocation === 'top' ? this.buildBatchActionsPanel() : '';\n const batchPanelBottom = this.batchBarLocation === 'bottom' ? this.buildBatchActionsPanel() : '';\n\n return `\n <div class=\"mojo-table-wrapper\">\n ${this.buildToolbarTemplate()}\n ${batchPanelTop}\n <div class=\"table-container\"${(() => { const __fs = (this.tableOptions && this.tableOptions.fontSize != null) ? this.tableOptions.fontSize : (this.options && this.options.fontSize); const __val = __fs === 'sm' ? '0.9rem' : (__fs === 'xs' ? '0.8rem' : (__fs ? String(__fs) : null)); return __val ? ` style=\"font-size: ${__val};\"` : ''; })()}>\n {{#loading}}\n <div class=\"mojo-table-loading d-flex justify-content-center align-items-center py-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"table-empty text-center py-5\">\n <i class=\"bi bi-inbox fa-2x mb-2 text-muted\"></i>\n <p class=\"text-muted\">{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <table class=\"${this.buildTableClasses()}\">\n ${this.buildTableHeaderTemplate()}\n <tbody data-container=\"items\"></tbody>\n ${this.hasFooterTotals ? this.buildTableFooterTemplate() : ''}\n </table>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${batchPanelBottom}\n ${this.buildPaginationTemplate()}\n </div>\n `;\n }\n\n /**\n * Build table CSS classes\n */\n buildTableClasses() {\n let classes = ['table'];\n\n if (this.tableOptions.striped) classes.push('table-striped');\n if (this.tableOptions.bordered) classes.push('table-bordered');\n if (this.tableOptions.hover) classes.push('table-hover');\n if (this.tableOptions.responsive) classes.push('table-responsive');\n if (this.tableOptions.background) classes.push(`table-${this.tableOptions.background}`);\n if (this.tableOptions.size === 'sm') classes.push('table-sm');\n if (this.tableOptions.size === 'lg') classes.push('table-lg');\n\n return classes.join(' ');\n }\n\n /**\n * Build toolbar template\n */\n buildToolbarTemplate() {\n if (!this.searchable && !this.filterable) {\n return '';\n }\n\n return `\n <div class=\"table-action-buttons mb-3\">\n <div class=\"d-flex align-items-center gap-2\">\n ${this.buildActionButtonsTemplate()}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n\n </div>\n <div data-container=\"filter-pills\"></div>\n </div>\n `;\n }\n\n /**\n * Build action buttons template\n */\n buildActionButtonsTemplate() {\n let buttons = [];\n\n // Refresh button\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-refresh\"\n data-action=\"refresh\"\n title=\"Refresh\">\n <i class=\"bi bi-arrow-clockwise\"></i>\n </button>\n `);\n\n // Fullscreen button (only if browser supports it)\n if (this.isFullscreenSupported()) {\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-fullscreen\"\n data-action=\"toggle-fullscreen\"\n title=\"Toggle Fullscreen\">\n <i class=\"bi bi-fullscreen\"></i>\n </button>\n `);\n }\n\n // Custom action buttons from options\n if (this.options.showAdd) {\n buttons.push(`\n <button class=\"btn btn-sm btn-success btn-add\"\n data-action=\"add\"\n title=\"${this.options.addButtonLabel}\">\n <i class=\"${this.options.addButtonIcon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${this.options.addButtonLabel}</span>\n </button>\n `);\n }\n\n if (this.options.showExport) {\n if (this.exportOptions && this.exportOptions.length > 1) {\n // Dropdown for multiple export options\n const dropdownItems = this.exportOptions.map(opt => `\n <li>\n <a class=\"dropdown-item\" href=\"#\" data-action=\"export\" data-format=\"${opt.format}\">\n <i class=\"${opt.icon || 'bi bi-file-earmark-arrow-down'} me-2\"></i>${opt.label}\n </a>\n </li>\n `).join('');\n\n buttons.push(`\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\" title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n <ul class=\"dropdown-menu\">\n ${dropdownItems}\n </ul>\n </div>\n `);\n } else {\n // Single export button\n const format = this.exportOptions && this.exportOptions.length === 1 ? this.exportOptions[0].format : 'json';\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-export\"\n data-action=\"export\"\n data-format=\"${format}\"\n title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n `);\n }\n }\n\n // if (buttons.length > 0) {\n // buttons.push(`<div class=\"vr mx-2\"></div>`);\n // }\n\n // Render custom toolbar buttons\n if (this.toolbarButtons && this.toolbarButtons.length > 0) {\n this.toolbarButtons.forEach((button, index) => {\n const {\n label = 'Button',\n icon = '',\n action = '',\n handler = null,\n variant = 'outline-secondary',\n title = label,\n className = '',\n permissions = null\n } = button;\n\n // Check permissions if specified\n if (permissions && !this.checkPermissions(permissions)) {\n return;\n }\n\n const iconHtml = icon ? `<i class=\"${icon} me-1\"></i>` : '';\n const labelHtml = `<span class=\"d-none d-lg-inline\">${label}</span>`;\n\n // Use handler if provided, otherwise use action for data-action attribute\n let dataAttrs = '';\n if (handler) {\n dataAttrs = `data-action=\"custom-toolbar-button\" data-button-index=\"${index}\"`;\n } else if (action) {\n dataAttrs = `data-action=\"${action}\"`;\n }\n\n const btnClass = `btn btn-sm btn-${variant} ${className}`.trim();\n\n buttons.push(`\n <button class=\"${btnClass}\"\n ${dataAttrs}\n title=\"${title}\">\n ${iconHtml}${labelHtml}\n </button>\n `);\n });\n }\n\n return buttons.join('');\n }\n\n /**\n * Build search template\n */\n buildSearchTemplate() {\n return `\n <div class=\"flex-grow-1\" style=\"max-width: 400px;\">\n <div class=\"input-group input-group-sm\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input type=\"search\"\n class=\"form-control\"\n placeholder=\"{{searchPlaceholder}}\"\n data-filter=\"search\"\n data-change-action=\"apply-search\"\n value=\"{{collection.params.search}}\"\n aria-label=\"Search\">\n {{#searchValue}}\n <button class=\"btn btn-outline-secondary\" type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\">\n <i class=\"bi bi-x\"></i>\n </button>\n {{/searchValue}}\n </div>\n </div>\n `;\n }\n\n /**\n * Build filter dropdown template\n */\n buildFilterDropdownTemplate() {\n const hasFilters = (this.filters && Object.keys(this.filters).length > 0) ||\n (this.additionalFilters && this.additionalFilters.length > 0);\n\n if (!hasFilters) {\n return '';\n }\n\n return `\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"bi bi-filter me-1\"></i>\n <span class=\"d-none d-lg-inline\">Add Filter</span>\n </button>\n <div class=\"dropdown-menu\" style=\"min-width: 250px;\">\n ${this.buildFilterList()}\n </div>\n </div>\n `;\n }\n\n /**\n * Build simple filter selection list\n */\n buildFilterList() {\n const allFilters = this.getAllAvailableFilters();\n const activeFilters = this.getActiveFilters();\n\n if (allFilters.length === 0) {\n return '<div class=\"dropdown-item-text text-muted\">No filters available</div>';\n }\n\n const filterItems = allFilters.map(filter => {\n const isActive = activeFilters.hasOwnProperty(filter.key);\n const activeClass = isActive ? 'active' : '';\n const icon = this.getFilterIcon(filter.type || filter.config?.type);\n\n return `\n <button class=\"dropdown-item ${activeClass}\"\n data-action=\"add-filter\"\n data-filter-key=\"${filter.key}\">\n <i class=\"bi bi-${icon} me-2\"></i>\n ${filter.label}\n ${isActive ? '<i class=\"bi bi-check-circle ms-auto\"></i>' : ''}\n </button>\n `;\n }).join('');\n\n return `\n ${filterItems}\n ${Object.keys(activeFilters).length > 0 ? `\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item text-danger\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-2\"></i>Clear All Filters\n </button>\n ` : ''}\n `;\n }\n\n /**\n * Update filter pills in the DOM\n */\n updateFilterPills() {\n const container = this.element?.querySelector('[data-container=\"filter-pills\"]');\n\n if (!container) {\n return;\n }\n\n const activeFilters = this.getActiveFilters();\n\n const pillsHTML = this.buildActivePills();\n container.innerHTML = pillsHTML;\n }\n\n /**\n * Update search input value across all search inputs\n */\n updateSearchInputs(value) {\n const searchInputs = this.element?.querySelectorAll('[data-filter=\"search\"]');\n if (searchInputs) {\n searchInputs.forEach(input => {\n input.value = value || '';\n });\n }\n }\n\n /**\n * Build active filter pills display\n */\n buildActivePills() {\n if (this.hideActivePills) {\n return '';\n }\n\n const activeFilters = this.getActiveFilters();\n const hasSearch = activeFilters.search && activeFilters.search.toString().trim() !== '';\n let filterEntries = Object.entries(activeFilters).filter(([key, value]) =>\n value && value.toString().trim() !== '' && key !== 'search'\n );\n\n // Hide specific pills based on configuration\n if (this.hideActivePillNames && this.hideActivePillNames.length > 0) {\n filterEntries = filterEntries.filter(([key]) =>\n !this.hideActivePillNames.includes(key)\n );\n }\n\n if (filterEntries.length === 0 && !hasSearch) {\n return '';\n }\n\n const pills = filterEntries.map(([paramKey, value]) => {\n const { field } = parseFilterKey(paramKey);\n const label = this.getFilterLabel(field);\n const displayText = formatFilterDisplay(paramKey, value, label);\n const icon = 'filter'; // search won't appear as pill anymore\n\n return `\n <span class=\"badge bg-primary me-1 mb-1 py-1 px-2 position-relative\" style=\"font-size: 0.75rem;\">\n <i class=\"bi bi-${icon} me-1\" style=\"font-size: 0.65rem;\"></i>\n\n <button type=\"button\" class=\"btn btn-link text-white p-0 ms-1\"\n style=\"font-size: 0.65rem; line-height: 1;\"\n data-action=\"edit-filter\"\n data-filter=\"${paramKey}\"\n title=\"Edit filter\">\n ${displayText}\n </button>\n\n <button type=\"button\" class=\"btn-close btn-close-white ms-1\"\n style=\"font-size: 0.6rem; width: 0.5rem; height: 0.5rem;\"\n data-action=\"remove-filter\"\n data-filter=\"${paramKey}\"\n title=\"Remove filter\">\n </button>\n </span>\n `;\n }).join('');\n\n // Show Clear All if there are multiple filters, or any filter + search\n const showClearAll = filterEntries.length > 1 || (filterEntries.length > 0 && hasSearch) || (filterEntries.length === 0 && hasSearch);\n const clearAllButton = showClearAll ? `\n <button class=\"btn btn-sm btn-outline-secondary mb-1 py-0 px-2\" style=\"font-size: 0.75rem;\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-1\" style=\"font-size: 0.7rem;\"></i>\n <small>Clear All</small>\n </button>\n ` : '';\n\n return `\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"d-flex flex-wrap align-items-center\">\n ${pills}\n ${clearAllButton}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Build table header template\n */\n buildTableHeaderTemplate() {\n let headerCells = '';\n\n // Selection checkbox header\n if (this.isSelectable()) {\n headerCells += `\n <th style=\"width: 40px; padding: 0;\">\n <div class=\"mojo-select-all-cell\" data-action=\"select-all\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </th>\n `;\n }\n\n // Column headers\n this.columns.forEach(column => {\n // Parse column key to get field name without pipes/formatters\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const sortable = this.sortable && column.sortable !== false;\n const currentSort = this.getSortBy() === fieldKey ? this.getSortDirection() : null;\n const sortIcon = this.getSortIcon(currentSort);\n const label = column.label || column.title || fieldKey;\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n const sortDropdown = sortable ? `\n <div class=\"dropdown d-inline-block ms-2\">\n <button class=\"btn btn-sm btn-link p-0 text-decoration-none\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\"\n data-column=\"${fieldKey}\">\n ${sortIcon}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n <li><a class=\"dropdown-item ${currentSort === 'asc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"asc\">\n <i class=\"bi bi-sort-alpha-down me-2\"></i>Sort A-Z\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === 'desc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"desc\">\n <i class=\"bi bi-sort-alpha-down-alt me-2\"></i>Sort Z-A\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === null ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"none\">\n <i class=\"bi bi-x-circle me-2\"></i>No Sort\n </a></li>\n </ul>\n </div>\n ` : '';\n\n headerCells += `\n <th class=\"${sortable ? 'sortable' : ''} ${responsiveClasses}\">\n <div class=\"d-flex align-items-center\">\n <span>${label}</span>\n ${sortDropdown}\n </div>\n </th>\n `;\n });\n\n // Actions header\n if (this.actions) {\n headerCells += '<th>Actions</th>';\n } else if (this.contextMenu) {\n headerCells += '<th style=\"width: 1px;\"></th>';\n }\n\n return `\n <thead>\n <tr>\n ${headerCells}\n </tr>\n </thead>\n `;\n }\n\n /**\n * Build table footer template with totals\n */\n buildTableFooterTemplate() {\n let footerCells = '';\n\n // Selection checkbox footer (empty)\n if (this.isSelectable()) {\n footerCells += '<td></td>';\n }\n\n // Column footers\n let totalColumnIndex = 0;\n this.columns.forEach((column, index) => {\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n if (column.footer_total) {\n // Use safe key for Mustache template\n const safeKey = `col_${totalColumnIndex}`;\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n\n let cellContent;\n if (formatter && typeof formatter === 'string') {\n cellContent = `{{{footerTotals.${safeKey}.value|${formatter}}}}`;\n } else {\n cellContent = `{{footerTotals.${safeKey}.value}}`;\n }\n\n footerCells += `<td class=\"table-footer-total ${responsiveClasses}\" data-total-column=\"${safeKey}\">${cellContent}</td>`;\n totalColumnIndex++;\n } else if (index === 0) {\n // First column shows \"Totals\" label\n footerCells += `<td class=\"table-footer-label ${responsiveClasses}\"><strong>Totals</strong></td>`;\n } else {\n // Empty cell for non-total columns\n footerCells += `<td class=\"${responsiveClasses}\"></td>`;\n }\n });\n\n // Actions footer (empty)\n if (this.actions) {\n footerCells += '<td></td>';\n } else if (this.contextMenu) {\n footerCells += '<td></td>';\n }\n\n return `\n <tfoot>\n <tr class=\"table-totals-row\">\n ${footerCells}\n </tr>\n </tfoot>\n `;\n }\n\n /**\n * Build batch actions panel\n */\n buildBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) {\n return '';\n }\n\n if (this.batchBarLocation === 'top') {\n // Toolbar-style batch actions for top placement\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"batch-${action.action}\" title=\"${action.label}\">\n <i class=\"${action.icon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${action.label}</span>\n </button>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel-top alert alert-info d-none mb-3\" role=\"alert\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"me-2\">\n <span class=\"batch-select-count\">0</span> ${this.options.batchPanelTitle || 'items'} selected\n </strong>\n </div>\n <div class=\"d-flex gap-2 align-items-center\">\n ${actionsHTML}\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"clear-selection\" title=\"Clear Selection\">\n <i class=\"bi bi-x-circle me-1\"></i>\n <span class=\"d-none d-lg-inline\">Clear</span>\n </button>\n </div>\n </div>\n </div>\n `;\n } else {\n // Original bottom panel style\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <div class=\"batch-select-action text-center px-2\" data-action=\"batch-${action.action}\">\n <div class=\"batch-action-icon fs-3\">\n <i class=\"${action.icon}\"></i>\n </div>\n <div class=\"batch-action-title small\">${action.label}</div>\n </div>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel rounded-start rounded-end\" style=\"display: none;\">\n <div class=\"batch-select-panel rounded-start rounded-end\">\n <div class=\"row g-0\">\n <div class=\"col-auto\">\n <div class=\"batch-select-count rounded-start\">0</div>\n </div>\n <div class=\"col\">\n <div class=\"ps-2 batch-select-title\">${this.options.batchPanelTitle || 'Rows'}</div>\n </div>\n <div class=\"col\">\n <div class=\"batch-select-actions d-flex justify-content-end\">\n ${actionsHTML}\n </div>\n </div>\n <div class=\"col-auto\">\n <div class=\"batch-select-end rounded-end\"></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n }\n\n /**\n * Build pagination template\n */\n buildPaginationTemplate() {\n if (!this.paginated) {\n return '';\n }\n\n return `\n <div class=\"table-status-bar mt-3\">\n <div class=\"d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3\">\n <div class=\"d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start\">\n <span class=\"text-muted\">\n Showing <span data-value=\"start\">0</span> to <span data-value=\"end\">0</span>\n of <span data-value=\"total\">0</span> entries\n </span>\n <div class=\"d-flex align-items-center\">\n <label class=\"form-label me-2 mb-0\">Show:</label>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\" data-change-action=\"page-size\">\n <option value=\"5\">5</option>\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n </div>\n <nav aria-label=\"Table pagination\">\n <ul class=\"pagination pagination-sm mb-0 justify-content-center\" data-container=\"pagination\">\n <!-- Pagination will be rendered here -->\n </ul>\n </nav>\n </div>\n </div>\n `;\n }\n\n /**\n * Override _createItemView to pass table-specific options\n */\n _createItemView(model, index) {\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n tableView: this, // Also pass as tableView for clarity\n template: this.itemTemplate,\n columns: this.columns,\n actions: this.actions,\n contextMenu: this.contextMenu,\n batchActions: this.batchActions,\n containerId: 'items'\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', (event) => {\n this._onItemSelect(event);\n this.updateBatchActionsPanel();\n });\n itemView.on('item:deselect', (event) => {\n this._onItemDeselect(event);\n this.updateBatchActionsPanel();\n });\n\n // Table-specific row events\n itemView.on('row:click', this._onRowClick.bind(this));\n itemView.on('row:view', this._onRowView.bind(this));\n itemView.on('row:edit', this._onRowEdit.bind(this));\n itemView.on('row:delete', this._onRowDelete.bind(this));\n itemView.on('cell:edit', this._onCellEdit.bind(this));\n itemView.on('cell:save', this._onCellSave.bind(this));\n itemView.on('cell:cancel', this._onCellCancel.bind(this));\n\n return itemView;\n }\n\n /**\n * Override onMounted to ensure filter pills are shown on initial load\n */\n async onMounted() {\n await super.onMounted();\n const activeFilters = this.getActiveFilters();\n\n // Ensure filter pills are displayed if there are active filters from URL\n if (this.collection && Object.keys(activeFilters).length > 0) {\n this.updateFilterPills();\n }\n\n // Add listener for native search clear button\n this.setupSearchClearListener();\n }\n\n /**\n * Setup listener for native search clear (X) button\n */\n setupSearchClearListener() {\n if (!this.element) return;\n\n const searchInputs = this.element.querySelectorAll('input[type=\"search\"][data-filter=\"search\"]');\n searchInputs.forEach(input => {\n // Listen for input event to detect native clear\n input.addEventListener('input', (event) => {\n // If value is empty and we had a search before, it was cleared\n if (event.target.value === '' && this.getActiveFilters().search) {\n this.onActionClearSearch(event, event.target);\n }\n });\n });\n }\n\n /**\n * Handle row click event\n */\n _onRowClick(event) {\n this.emit('row:click', event);\n\n // Default behavior - show item details if configured\n if (this.options.onRowClick) {\n return this.options.onRowClick(event.model, event.event);\n }\n\n if (this.clickAction === 'view') {\n this._onRowView(event);\n } else if (this.clickAction === 'edit') {\n this._onRowEdit(event);\n }\n }\n\n /**\n * Get the Model class from collection or instance\n */\n getModelClass(model) {\n // Try to get from collection first\n if (this.collection?.ModelClass) return this.collection.ModelClass;\n if (this.collection?.model) return this.collection.model;\n\n // Try to get from a model instance\n if (model?.constructor) return model.constructor;\n\n // Return null if we can't determine\n return null;\n }\n\n /**\n * Get model name for display\n */\n getModelName(model) {\n const ModelClass = this.getModelClass(model);\n if (!ModelClass) return 'Item';\n\n return ModelClass.MODEL_NAME ||\n ModelClass.name.replace(/Model$/, '') ||\n 'Item';\n }\n\n /**\n * Resolve item view class with fallbacks\n */\n getItemViewClass(model) {\n // Check instance options first\n if (this.itemView) return this.itemView;\n\n // Check Model class static property\n const ModelClass = this.getModelClass(model);\n if (ModelClass?.VIEW_CLASS) return ModelClass.VIEW_CLASS;\n\n return null; // Will use data view as fallback\n }\n\n /**\n * Resolve add form configuration with fallbacks\n */\n getAddFormConfig(ModelClass) {\n return this.addForm ||\n ModelClass?.ADD_FORM ||\n this.editForm ||\n ModelClass?.EDIT_FORM;\n }\n\n /**\n * Resolve edit form configuration with fallbacks\n */\n getEditFormConfig(ModelClass) {\n return this.editForm ||\n ModelClass?.EDIT_FORM ||\n this.addForm ||\n ModelClass?.ADD_FORM;\n }\n\n /**\n * Get form dialog configuration\n */\n getFormDialogConfig(ModelClass) {\n return {\n ...ModelClass?.FORM_DIALOG_CONFIG,\n ...this.formDialogConfig\n };\n }\n\n /**\n * Render a template string with model context\n */\n renderTemplateString(template, model) {\n if (!template) return '';\n\n // Use Mustache to render the template with the model as context\n return Mustache.render(template, model);\n }\n\n /**\n * Handle row view action\n */\n async _onRowView(event) {\n this.emit('row:view', event);\n\n // Check for custom handler first\n if (this.options.onItemView) {\n await this.options.onItemView(event.model, event.event);\n return;\n }\n\n const ViewClass = this.getItemViewClass(event.model);\n\n if (ViewClass) {\n // Use custom view class\n const viewInstance = new ViewClass({ model: event.model, collection: this.collection });\n await Dialog.showDialog({\n header: false,\n body: viewInstance,\n size: 'lg',\n centered: false,\n ...this.getFormDialogConfig(this.getModelClass(event.model)),\n ...this.viewDialogOptions\n });\n } else {\n // Fallback to data view\n await Dialog.showData({\n title: `View ${this.getModelName(event.model)} #${event.model.id}`,\n model: event.model\n });\n }\n }\n\n /**\n * Handle row edit action\n */\n async _onRowEdit(event) {\n this.emit('row:edit', event);\n\n // Check for custom handler first\n if (this.options.onItemEdit) {\n await this.options.onItemEdit(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n let formConfig = this.getEditFormConfig(ModelClass);\n\n if (formConfig) {\n if (!formConfig.fields) {\n formConfig = { title: `Edit ${this.getModelName(event.model)}`, fields: formConfig };\n }\n\n const result = await Dialog.showModelForm({\n model: event.model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (!result) return; // Cancelled\n\n if (!result.success || !result?.result?.data.status) {\n Dialog.showError(result?.result?.data?.error || result?.result?.message || \"An error occurred\");\n return;\n }\n\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const result = await Dialog.showDialog({\n title: `Edit ${this.getModelName(event.model)} #${event.model.id}`,\n body: new FormView({\n model: event.model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await event.model.save(result);\n if (!resp.data?.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle row delete action\n */\n async _onRowDelete(event) {\n this.emit('row:delete', event);\n\n // Check for custom handler first\n if (this.options.onItemDelete) {\n await this.options.onItemDelete(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n\n // Get delete template from options, Model class, or use default\n const template = this.deleteTemplate ||\n ModelClass?.DELETE_TEMPLATE ||\n 'Are you sure you want to delete this {{name||\"item\"}}?';\n\n // Render template with model context\n const message = this.renderTemplateString(template, event.model);\n\n const confirmed = await Dialog.confirm({\n message: message || 'Are you sure you want to delete this item?',\n title: 'Confirm Delete',\n confirmText: 'Delete',\n confirmClass: 'btn-danger'\n });\n\n if (confirmed) {\n await event.model.destroy();\n this.collection.fetch();\n }\n }\n\n /**\n * Handle cell edit event\n */\n _onCellEdit(event) {\n this.emit('cell:edit', event);\n }\n\n /**\n * Handle cell save event\n */\n async _onCellSave(event) {\n this.emit('cell:save', event);\n // Model save is now handled directly in TableRow.saveCellEdit()\n }\n\n /**\n * Handle cell cancel event\n */\n _onCellCancel(event) {\n this.emit('cell:cancel', event);\n }\n\n /**\n * Check if fullscreen is supported by the browser\n */\n isFullscreenSupported() {\n return !!(\n document.fullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.msFullscreenEnabled\n );\n }\n\n /**\n * Handle toggle fullscreen action\n */\n async onActionToggleFullscreen(event, element) {\n if (this.isFullscreen) {\n await this.exitFullscreen();\n } else {\n await this.enterFullscreen();\n }\n }\n\n /**\n * Enter fullscreen mode\n */\n async enterFullscreen() {\n try {\n // Use browser's native fullscreen API\n if (this.element.requestFullscreen) {\n await this.element.requestFullscreen();\n } else if (this.element.mozRequestFullScreen) {\n await this.element.mozRequestFullScreen();\n } else if (this.element.webkitRequestFullscreen) {\n await this.element.webkitRequestFullscreen();\n } else if (this.element.msRequestFullscreen) {\n await this.element.msRequestFullscreen();\n }\n\n this.isFullscreen = true;\n this.element.classList.add('table-fullscreen');\n this.updateFullscreenButton();\n\n // Listen for fullscreen change events\n this.setupFullscreenListeners();\n\n this.emit('table:fullscreen:enter');\n\n } catch (error) {\n console.warn('Could not enter fullscreen:', error);\n }\n }\n\n /**\n * Exit fullscreen mode\n */\n async exitFullscreen() {\n try {\n if (document.exitFullscreen) {\n await document.exitFullscreen();\n } else if (document.mozCancelFullScreen) {\n await document.mozCancelFullScreen();\n } else if (document.webkitExitFullscreen) {\n await document.webkitExitFullscreen();\n } else if (document.msExitFullscreen) {\n await document.msExitFullscreen();\n }\n\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n\n this.emit('table:fullscreen:exit');\n\n } catch (error) {\n console.warn('Could not exit fullscreen:', error);\n }\n }\n\n /**\n * Update fullscreen button icon and title\n */\n updateFullscreenButton() {\n const button = this.element?.querySelector('.btn-fullscreen');\n const icon = button?.querySelector('i');\n\n if (button && icon) {\n if (this.isFullscreen) {\n icon.className = 'bi bi-fullscreen-exit';\n button.title = 'Exit Fullscreen';\n } else {\n icon.className = 'bi bi-fullscreen';\n button.title = 'Enter Fullscreen';\n }\n }\n }\n\n /**\n * Setup fullscreen event listeners\n */\n setupFullscreenListeners() {\n // Don't add listeners multiple times\n if (this._fullscreenHandler) return;\n\n const handleFullscreenChange = () => {\n const isCurrentlyFullscreen = !!(\n document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement ||\n document.msFullscreenElement\n );\n\n if (!isCurrentlyFullscreen && this.isFullscreen) {\n // User exited fullscreen via ESC or browser controls\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n this.emit('table:fullscreen:exit');\n }\n };\n\n // Add listeners for all browser prefixes\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n document.addEventListener('msfullscreenchange', handleFullscreenChange);\n\n // Store handler for cleanup\n this._fullscreenHandler = handleFullscreenChange;\n }\n\n /**\n * Cleanup fullscreen listeners\n */\n cleanupFullscreenListeners() {\n if (this._fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this._fullscreenHandler);\n document.removeEventListener('mozfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('webkitfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('msfullscreenchange', this._fullscreenHandler);\n this._fullscreenHandler = null;\n }\n }\n\n /**\n * Override destroy to cleanup fullscreen listeners\n */\n destroy() {\n this.cleanupFullscreenListeners();\n super.destroy();\n }\n\n /**\n * Handle refresh action\n */\n async onActionRefresh(event, element) {\n await this.refresh();\n }\n\n /**\n * Handle add action\n */\n async onActionAdd(event, element) {\n // Check for custom handler first - if provided, just emit event and let handler deal with it\n if (this.options.onAdd) {\n this.emit('table:add', { event });\n await this.options.onAdd(event);\n return;\n }\n\n // Emit event for external listeners\n this.emit('table:add', { event });\n\n const ModelClass = this.getModelClass();\n if (!ModelClass) {\n console.warn('Cannot determine Model class for add operation');\n return;\n }\n\n let formConfig = this.getAddFormConfig(ModelClass);\n\n if (formConfig) {\n const model = new ModelClass();\n if (!formConfig.fields) {\n formConfig = { title: `Add ${this.getModelName()}`, fields: formConfig };\n }\n\n const result = await Dialog.showForm({\n model: model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (result) {\n if (this.options.addRequiresActiveGroup) {\n result.group = this.getApp().activeGroup.id;\n }\n if (this.options.addRequiresActiveUser) {\n result.user = this.getApp().activeUser.id;\n }\n if (this.options.addFormDefaults) {\n Object.assign(result, this.options.addFormDefaults);\n }\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp?.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const model = new ModelClass();\n\n const result = await Dialog.showDialog({\n title: `Add ${this.getModelName()}`,\n body: new FormView({\n model: model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle export action\n */\n async onActionExport(event, element) {\n const format = element.getAttribute('data-format') || 'json';\n\n this.emit('table:export', {\n format: format,\n source: this.exportSource,\n event\n });\n\n if (this.exportSource === 'remote') {\n if (this.collection) {\n await this.collection.download(format);\n } else {\n console.warn('TableView: Cannot export from remote without a collection.');\n }\n } else {\n // Handle local export (future enhancement)\n if (this.options.onExport) {\n await this.options.onExport(this.collection?.toJSON() || [], format);\n } else {\n console.warn('TableView: onExport handler not implemented for local export.');\n }\n }\n }\n\n /**\n * Handle search action (Enter key triggers this via EventDelegate)\n */\n async onActionApplySearch(event, element) {\n const searchTerm = element.value.trim();\n\n if (this.collection) {\n this.setFilter('search', searchTerm);\n\n // Reset to first page when searching\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side filtering\n this.render();\n }\n }\n\n // Update filter pills when search changes\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear search button\n */\n async onActionClearSearch(event, element) {\n // Clear the search filter\n this.setFilter('search', null);\n\n // Reset to first page\n if (this.collection) {\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n }\n\n // Render will rebuild the search input with empty value\n await this.render();\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm: '', event });\n this.emit('params-changed');\n }\n\n /**\n * Get current sort field\n */\n getSortBy() {\n const sort = this.collection?.params?.sort;\n if (!sort) return null;\n return sort.startsWith('-') ? sort.slice(1) : sort;\n }\n\n /**\n * Get current sort direction\n */\n getSortDirection() {\n const sort = this.collection?.params?.sort;\n if (!sort) return 'asc';\n return sort.startsWith('-') ? 'desc' : 'asc';\n }\n\n /**\n * Get sort icon based on current sort direction\n */\n getSortIcon(direction) {\n if (direction === 'asc') {\n return '<i class=\"bi bi-sort-alpha-down text-primary\"></i>';\n } else if (direction === 'desc') {\n return '<i class=\"bi bi-sort-alpha-down-alt text-primary\"></i>';\n } else {\n return '<i class=\"bi bi-three-dots-vertical text-muted\"></i>';\n }\n }\n\n /**\n * Handle sort action\n */\n async onActionSort(event, element) {\n event.preventDefault();\n const field = element.getAttribute('data-field');\n const direction = element.getAttribute('data-direction');\n\n if (this.collection) {\n let newSort;\n\n if (direction === 'none') {\n newSort = undefined; // Remove sort\n } else if (direction === 'desc') {\n newSort = `-${field}`; // Descending sort\n } else {\n newSort = field; // Ascending sort\n }\n\n this.collection.setParams({\n ...this.collection.params,\n sort: newSort,\n start: 0 // Reset to first page when sorting changes\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side sorting\n if (newSort) {\n const desc = newSort.startsWith('-');\n const sortField = desc ? newSort.slice(1) : newSort;\n\n this.collection.sort((a, b) => {\n const aVal = a.get(sortField);\n const bVal = b.get(sortField);\n\n if (aVal < bVal) return desc ? 1 : -1;\n if (aVal > bVal) return desc ? -1 : 1;\n return 0;\n });\n }\n\n this.render();\n }\n }\n\n // Update sort icons in the DOM\n this.updateSortIcons();\n\n this.emit('table:sort', { field, event });\n this.emit('params-changed');\n }\n\n /**\n * Update sort icons in all column headers\n */\n updateSortIcons() {\n if (!this.element) return;\n\n const currentSortField = this.getSortBy();\n const currentSortDir = this.getSortDirection();\n\n // Update all sort dropdown buttons\n this.columns.forEach(column => {\n if (this.sortable && column.sortable !== false) {\n // Parse the column key to get just the field name (without pipes/formatters)\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const dropdown = this.element.querySelector(`[data-bs-toggle=\"dropdown\"][data-column=\"${fieldKey}\"]`);\n if (dropdown) {\n const isSorted = currentSortField === fieldKey;\n const sortIcon = this.getSortIcon(isSorted ? currentSortDir : null);\n dropdown.innerHTML = sortIcon;\n\n // Update dropdown menu items\n const dropdownMenu = dropdown.nextElementSibling;\n if (dropdownMenu) {\n const ascItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"asc\"]`);\n const descItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"desc\"]`);\n const noneItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"none\"]`);\n\n if (ascItem) {\n ascItem.classList.toggle('active', isSorted && currentSortDir === 'asc');\n }\n if (descItem) {\n descItem.classList.toggle('active', isSorted && currentSortDir === 'desc');\n }\n if (noneItem) {\n noneItem.classList.toggle('active', !isSorted || currentSortField !== fieldKey);\n }\n }\n }\n }\n });\n }\n\n /**\n * Handle select all action\n */\n async onActionSelectAll(event, element) {\n event.stopPropagation();\n const isCurrentlyAllSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n\n if (!isCurrentlyAllSelected) {\n // Select all visible items\n this.forEachItem(itemView => {\n if (!itemView.selected) {\n itemView.select();\n }\n });\n } else {\n // Deselect all\n this.clearSelection();\n }\n\n // Update select all checkbox visual state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n selectAllCell.classList.toggle('selected', !isCurrentlyAllSelected);\n }\n\n // Update batch actions panel\n this.updateBatchActionsPanel();\n }\n\n /**\n * Override onBeforeRender to set data properties before rendering\n */\n async onBeforeRender() {\n // Set properties that Mustache needs\n this.searchValue = this.getActiveFilters().search || '';\n this.footerTotals = this.calculateFooterTotals();\n }\n\n /**\n * Override onAfterRender to update pagination info\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Update footer totals in case collection loaded after initial render\n if (this.hasFooterTotals) {\n this.updateFooterTotals();\n }\n\n // Update pagination info\n if (this.paginated && this.collection) {\n const total = this.collection.meta?.count || this.collection.length();\n const start = this.collection.params?.start || 0;\n const size = this.collection.params?.size || 10;\n const end = Math.min(start + size, total);\n\n const startEl = this.element.querySelector('[data-value=\"start\"]');\n const endEl = this.element.querySelector('[data-value=\"end\"]');\n const totalEl = this.element.querySelector('[data-value=\"total\"]');\n\n if (startEl) startEl.textContent = start + 1;\n if (endEl) endEl.textContent = end;\n if (totalEl) totalEl.textContent = total;\n\n // Update page size selector\n const pageSizeSelect = this.element.querySelector('[data-change-action=\"page-size\"]');\n if (pageSizeSelect) {\n pageSizeSelect.value = size;\n }\n\n // Render pagination controls\n this.renderPagination();\n }\n\n // Update sort icons after render\n this.updateSortIcons();\n\n // Update filter pills after render - this is crucial for showing pills on page load\n this.updateFilterPills();\n\n // Re-setup search clear listener after render\n this.setupSearchClearListener();\n }\n\n /**\n * Render pagination controls\n * - Prev/Next wrap around (never disabled)\n * - Truncated page list with first/last and ellipses\n */\n renderPagination() {\n const paginationContainer = this.element.querySelector('[data-container=\"pagination\"]');\n if (!paginationContainer || !this.collection) return;\n\n const total = this.collection.meta?.count || this.collection.length();\n const size = this.collection.params?.size || 10;\n const start = this.collection.params?.start || 0;\n const currentPage = Math.floor(start / size) + 1;\n const totalPages = Math.ceil(total / size);\n\n if (totalPages <= 1) {\n paginationContainer.innerHTML = '';\n return;\n }\n\n const prevPage = currentPage > 1 ? currentPage - 1 : totalPages;\n const nextPage = currentPage < totalPages ? currentPage + 1 : 1;\n\n const pages = [];\n\n // Previous (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${prevPage}\">\n <i class=\"bi bi-chevron-left\"></i>\n </a>\n </li>\n `);\n\n // Build truncated page list: always show 1 and totalPages, with neighbors around current\n const neighbors = 1; // how many pages to show on each side of current\n const visibleSet = new Set([1, totalPages]);\n for (let i = currentPage - neighbors; i <= currentPage + neighbors; i++) {\n if (i >= 1 && i <= totalPages) visibleSet.add(i);\n }\n const visible = Array.from(visibleSet).sort((a, b) => a - b);\n\n // Render pages with ellipses where there are gaps\n let last = 0;\n for (const p of visible) {\n if (last && p - last > 1) {\n // gap -> ellipsis\n pages.push(`\n <li class=\"page-item disabled\"><span class=\"page-link\">…</span></li>\n `);\n }\n pages.push(`\n <li class=\"page-item ${p === currentPage ? 'active' : ''}\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${p}\">${p}</a>\n </li>\n `);\n last = p;\n }\n\n // Next (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${nextPage}\">\n <i class=\"bi bi-chevron-right\"></i>\n </a>\n </li>\n `);\n\n paginationContainer.innerHTML = pages.join('');\n }\n\n /**\n * Handle page change\n * - Normalizes and wraps page number (1..totalPages)\n */\n async onActionPage(event, element) {\n event.preventDefault();\n\n const rawPage = parseInt(element.getAttribute('data-page'), 10);\n const size = this.collection.params?.size || 10;\n const total = this.collection.meta?.count || this.collection.length();\n const totalPages = Math.max(1, Math.ceil(total / size));\n\n let page = isNaN(rawPage) ? 1 : rawPage;\n if (page < 1) page = totalPages;\n if (page > totalPages) page = 1;\n\n this.collection.setParams({\n ...this.collection.params,\n start: (page - 1) * size\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n this.render();\n }\n\n this.emit('table:page', { page, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle page size change\n */\n async onChangePageSize(event, element) {\n const newSize = parseInt(element.value);\n\n if (this.collection) {\n // Reset to first page when changing page size\n this.collection.setParams({\n ...this.collection.params,\n start: 0,\n size: newSize\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n }\n\n this.emit('table:pagesize', { size: newSize, event });\n this.emit('params-changed');\n }\n\n /**\n * Get active filters from collection params\n */\n getActiveFilters() {\n if (!this.collection?.params) {\n return {};\n }\n const { start, size, sort, ...allParams } = this.collection.params;\n const filters = {};\n\n // Reconstruct daterange filters from their component parts\n const processedKeys = new Set();\n\n // First pass: identify and process daterange filters\n const allFilterConfigs = this.getAllAvailableFilters();\n allFilterConfigs.forEach(filterDef => {\n if (filterDef.config.type === 'daterange') {\n const key = filterDef.key;\n const startName = filterDef.config.startName || 'dr_start';\n const endName = filterDef.config.endName || 'dr_end';\n const fieldName = filterDef.config.fieldName || 'dr_field';\n\n // Check if this daterange filter is active for this specific key\n if (allParams[fieldName] === key && (allParams[startName] || allParams[endName])) {\n filters[key] = {\n start: allParams[startName] || '',\n end: allParams[endName] || ''\n };\n\n processedKeys.add(startName);\n processedKeys.add(endName);\n processedKeys.add(fieldName);\n }\n }\n });\n\n // Second pass: add remaining filters\n Object.keys(allParams).forEach(paramKey => {\n if (!processedKeys.has(paramKey)) {\n filters[paramKey] = allParams[paramKey];\n }\n });\n\n // Normalize single-value vs multi-value filters where both exist (e.g., field and field__in)\n Object.keys(filters).forEach(key => {\n if (filters.hasOwnProperty(key)) {\n const inKey = `${key}__in`;\n if (filters.hasOwnProperty(inKey)) {\n // Prefer __in when explicitly provided; remove the base key to avoid duplicate pills\n delete filters[key];\n filters[inKey] = filters[inKey];\n }\n }\n });\n\n return filters;\n }\n\n /**\n * Set a filter value\n */\n setFilter(key, value) {\n if (!this.collection) return;\n\n const filterConfig = this.getFilterConfig(key);\n\n // Handle daterange filters specially\n if (filterConfig && filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n const fieldName = filterConfig.fieldName || 'dr_field';\n\n // Always remove old values first\n delete this.collection.params[startName];\n delete this.collection.params[endName];\n delete this.collection.params[fieldName];\n\n // Set new values if provided and not empty\n if (value && typeof value === 'object' && (value.start || value.end)) {\n if (value.start) this.collection.params[startName] = value.start;\n if (value.end) this.collection.params[endName] = value.end;\n this.collection.params[fieldName] = key;\n }\n } else {\n // Parse key to get field and lookup\n const { field, lookup } = parseFilterKey(key);\n\n // Clear old values - remove both base field and variants\n delete this.collection.params[key];\n delete this.collection.params[field];\n delete this.collection.params[`${field}__in`];\n\n if (!value || (Array.isArray(value) && value.length === 0)) {\n return; // Cleared\n }\n\n // Smart param generation for multiselect fields\n if (Array.isArray(value)) {\n if (value.length === 1) {\n // Single value from array - use simple key (no __in)\n this.collection.params[field] = value[0];\n } else {\n // Multiple values - use __in lookup\n this.collection.params[`${field}__in`] = value.join(',');\n }\n } else {\n // Single value - use key as-is (may include lookup like __gte)\n this.collection.params[key] = value;\n }\n }\n }\n\n /**\n * Get all available filters\n */\n getAllAvailableFilters() {\n const filters = [];\n\n // Add column-based filters\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n filters.push({\n key: fieldKey,\n label: column.filter.label || column.label || fieldKey,\n type: column.filter.type,\n config: column.filter\n });\n }\n });\n\n // Add additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n this.additionalFilters.forEach(filter => {\n filters.push({\n key: filter.name || filter.key,\n label: filter.label,\n type: filter.type,\n config: filter\n });\n });\n }\n\n return filters;\n }\n\n /**\n * Get filter configuration for a key\n */\n getFilterConfig(filterKey) {\n // Check column filters first\n const column = this.columns.find(col => {\n const { fieldKey } = this.parseColumnKey(col.key);\n return fieldKey === filterKey;\n });\n if (column && column.filter) {\n return column.filter;\n }\n\n // Check additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n const filter = this.additionalFilters.find(f => (f.name || f.key) === filterKey);\n if (filter) {\n return filter;\n }\n }\n\n return null;\n }\n\n /**\n * Get filter label\n */\n getFilterLabel(key) {\n if (key === 'search') return 'Search';\n\n const filter = this.filters[key];\n if (filter && filter.label) return filter.label;\n\n const additionalFilter = this.additionalFilters.find(f =>\n (f.name || f.key) === key\n );\n if (additionalFilter && additionalFilter.label) return additionalFilter.label;\n\n return key.charAt(0).toUpperCase() + key.slice(1);\n }\n\n /**\n * Get filter display value\n */\n getFilterDisplayValue(key, value) {\n if (key === 'search') return `\"${value}\"`;\n\n const filter = this.filters[key] ||\n this.additionalFilters.find(f => (f.name || f.key) === key);\n\n if (filter && filter.type === 'daterange' && typeof value === 'object') {\n const start = value.start || '';\n const end = value.end || '';\n return `${start} to ${end}`;\n }\n\n if (filter && filter.type === 'select' && filter.options) {\n if (typeof filter.options[0] === 'object') {\n const option = filter.options.find(opt => opt.value === value);\n return option ? option.label : value;\n }\n return value;\n }\n\n return value;\n }\n\n /**\n * Get icon for filter type\n */\n getFilterIcon(type) {\n const icons = {\n 'text': 'search',\n 'select': 'funnel',\n 'date': 'calendar',\n 'daterange': 'calendar-range',\n 'number': '123',\n 'boolean': 'toggle-on'\n };\n return icons[type] || 'filter';\n }\n\n /**\n * Handle add filter action\n */\n async onActionAddFilter(event, element) {\n const filterKey = element.getAttribute('data-filter-key');\n const filterConfig = this.getFilterConfig(filterKey);\n const currentValue = this.getActiveFilters()[filterKey];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey);\n return;\n }\n\n // Using statically imported Dialog\n\n // Show dialog for this specific filter\n const result = await Dialog.showForm({\n title: `${currentValue !== undefined && currentValue !== '' ? 'Edit' : 'Add'} ${this.getFilterLabel(filterKey)} Filter`,\n size: 'md',\n fields: [this.buildFilterDialogField(filterConfig, currentValue, filterKey)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n // Use the filter key for setFilter (it will handle the lookup internally)\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Build filter dialog field configuration\n */\n buildFilterDialogField(filterConfig, currentValue, filterKey) {\n const field = {\n name: 'filter_value',\n label: filterConfig.label,\n value: currentValue,\n ...filterConfig,\n // Ensure placeholder is passed through (support both casings)\n placeholder: filterConfig.placeholder || filterConfig.placeHolder\n };\n\n // Set current value appropriately based on filter type\n if (filterConfig.type === 'daterange') {\n // Apply defaults for daterange\n field.startName = field.startName || 'dr_start';\n field.endName = field.endName || 'dr_end';\n field.fieldName = field.fieldName || 'dr_field';\n field.format = field.format || 'YYYY-MM-DD';\n field.displayFormat = field.displayFormat || 'MMM DD, YYYY';\n field.separator = field.separator || ' to ';\n field.label = field.label || 'Date Range';\n\n // Handle daterange current values\n if (currentValue && typeof currentValue === 'object') {\n const normalizeDateValue = (val) => {\n if (!val && val !== 0) return '';\n if (val instanceof Date && !isNaN(val)) {\n return val.toISOString().slice(0, 10);\n }\n\n const str = String(val).trim();\n if (!str) return '';\n\n // Numeric timestamps (seconds or milliseconds)\n if (/^-?\\d+$/.test(str)) {\n const num = Number(str);\n const ms = str.length <= 10 ? num * 1000 : num;\n const date = new Date(ms);\n if (!isNaN(date)) {\n return date.toISOString().slice(0, 10);\n }\n }\n\n // ISO or other parseable formats\n const date = new Date(str);\n if (!isNaN(date)) {\n return date.toISOString().slice(0, 10);\n }\n\n // Fallback: return original string\n return str;\n };\n\n field.startDate = normalizeDateValue(currentValue.start || currentValue.from || currentValue.begin || '');\n field.endDate = normalizeDateValue(currentValue.end || currentValue.to || currentValue.finish || '');\n }\n } else if (filterConfig.type === 'multiselect') {\n // Convert comma-separated string to array for multiselect\n let valueArray = [];\n if (currentValue) {\n if (Array.isArray(currentValue)) {\n valueArray = currentValue;\n } else if (typeof currentValue === 'string') {\n // Split by comma and trim whitespace\n valueArray = currentValue.split(',').map(v => v.trim()).filter(v => v);\n }\n }\n\n field.value = valueArray;\n\n // Ensure placeholder is set (support both casings)\n if (!field.placeholder && !field.placeHolder) {\n if (filterConfig.placeholder || filterConfig.placeHolder) {\n field.placeholder = filterConfig.placeholder || filterConfig.placeHolder;\n } else if (filterConfig.label) {\n field.placeholder = `Select ${filterConfig.label}...`;\n }\n }\n }\n\n return field;\n }\n\n /**\n * Extract filter value from form result\n */\n extractFilterValue(filterConfig, formResult) {\n if (filterConfig.type === 'daterange') {\n // Extract start/end values based on naming convention\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n\n const result = {\n start: formResult[startName],\n end: formResult[endName]\n };\n\n return result;\n }\n\n if (filterConfig.type === 'multiselect') {\n // Return array as-is for multiselect\n return formResult.filter_value;\n }\n\n return formResult.filter_value;\n }\n\n /**\n * Apply filters to collection and refresh\n */\n async applyFilters() {\n // Reset to first page when filters change\n if (this.collection) {\n this.collection.params.start = 0;\n }\n\n // For REST collections, fetch data with new filters\n if (this.collection?.restEnabled) {\n try {\n await this.collection.fetch();\n this.render();\n } catch (error) {\n console.error('Failed to fetch filtered data:', error);\n this.render();\n }\n } else {\n this.render();\n }\n\n // Update filter pills display\n this.updateFilterPills();\n\n // Emit params changed event for URL synchronization\n this.emit('params-changed');\n }\n\n /**\n * Handle edit filter action from pill\n */\n async onActionEditFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n\n // Parse the key to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n\n // Try to get filter config using the parsed field name first, then original key\n let filterConfig = this.getFilterConfig(field) || this.getFilterConfig(filterKey);\n\n // Get current value - could be under filterKey or field\n const activeFilters = this.getActiveFilters();\n const currentValue = activeFilters[filterKey] || activeFilters[field];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey, 'or field:', field);\n return;\n }\n\n // Prepare initial form data\n const formData = { filter_value: currentValue };\n if (filterConfig.type === 'daterange' && currentValue && typeof currentValue === 'object') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n formData[startName] = currentValue.start || '';\n formData[endName] = currentValue.end || '';\n }\n\n // Show mini dialog for this specific filter\n const result = await Dialog.showForm({\n title: `Edit ${this.getFilterLabel(field)} Filter`,\n size: 'md',\n data: formData,\n fields: [this.buildFilterDialogField(filterConfig, currentValue, field)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Handle remove filter action\n */\n async onActionRemoveFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n\n // Parse to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n\n // Clear the filter using the original key\n this.setFilter(filterKey, null);\n\n // If removing search filter, clear search inputs\n if (filterKey === 'search') {\n this.updateSearchInputs('');\n }\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after removing\n this.updateFilterPills();\n\n this.emit('filter:remove', { key: filterKey, field });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear all filters action\n */\n async onActionClearAllFilters(event, element) {\n if (!this.collection) return;\n\n // Clear all filters except pagination and sorting\n const { start, size, sort } = this.collection.params;\n this.collection.params = { start, size };\n if (sort) this.collection.params.sort = sort;\n\n // Clear all search inputs\n this.updateSearchInputs('');\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after clearing\n this.updateFilterPills();\n\n this.emit('filters:clear');\n this.emit('params-changed');\n }\n\n /**\n * Update batch actions panel visibility and count\n */\n updateBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) return;\n\n const selectedCount = this.getSelectedItems().length;\n\n if (this.batchBarLocation === 'top') {\n // Handle top panel style\n const panel = this.element?.querySelector('.batch-actions-panel-top');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n\n // Use Bootstrap's d-none class for cleaner show/hide\n if (selectedCount > 0) {\n panel.classList.remove('d-none');\n } else {\n panel.classList.add('d-none');\n }\n }\n } else {\n // Handle bottom panel style (original)\n const panel = this.element?.querySelector('.batch-actions-panel');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n panel.style.display = selectedCount > 0 ? 'block' : 'none';\n }\n }\n\n // Update select all checkbox state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n const allSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n const someSelected = Array.from(this.itemViews.values()).some(item => item.selected);\n\n selectAllCell.classList.toggle('selected', allSelected);\n selectAllCell.classList.toggle('indeterminate', !allSelected && someSelected);\n\n // Update icon for indeterminate state\n const icon = selectAllCell.querySelector('i');\n if (icon) {\n icon.className = !allSelected && someSelected ? 'bi bi-dash' : 'bi bi-check';\n }\n }\n }\n\n /**\n * Handle batch action clicks\n */\n async onActionBatch(event, element) {\n const batchAction = element.getAttribute('data-action').replace('batch-', '');\n const selectedItems = this.getSelectedItems();\n\n this.emit('batch:action', {\n action: batchAction,\n items: selectedItems,\n event\n });\n }\n\n /**\n * Handle clear selection action (for top batch bar)\n */\n async onActionClearSelection(event, element) {\n this.clearSelection();\n this.updateBatchActionsPanel();\n }\n\n /**\n * Handle custom toolbar button with handler function\n */\n async onActionCustomToolbarButton(event, element) {\n const buttonIndex = parseInt(element.getAttribute('data-button-index'), 10);\n const button = this.toolbarButtons[buttonIndex];\n\n if (button && typeof button.handler === 'function') {\n await button.handler.call(this, event, element);\n }\n }\n}\n\nexport default TableView;\n"],"names":["Log","Model","constructor","data","super","endpoint","LogList","Collection","options","ModelClass","size","Member","hasPermission","permission","Array","isArray","some","p","this","permissions","get","fetchForGroup","groupId","fetch","url","MemberList","MemberForms","edit","title","fields","name","type","label","placeholder","columns","EDIT_FORM","ADD_FORM","create","TableRow","ListViewItem","tagName","className","enableTooltips","actions","contextMenu","batchActions","tableView","listView","editingCells","Set","template","buildRowTemplate","getResponsiveClasses","visibility","validBreakpoints","includes","console","warn","join","classes","hide","push","show","isSelectable","forEach","column","columnIndex","combinedClasses","class","editable","filter","c","cellContent","buildCellTemplate","cellAction","action","rowAction","key","buildActionsTemplate","buildContextMenuTemplate","path","formatter","format","length","map","model","id","icon","buildContextMenuItems","menuItem","separator","divider","itemClass","danger","disabled","onAfterRender","cell","element","querySelector","value","context","row","table","index","innerHTML","error","selected","classList","add","setAttribute","onActionEditCell","event","stopPropagation","columnKey","getAttribute","find","col","has","enterEditMode","onActionRowClick","target","closest","emit","onActionView","onActionEdit","onActionDelete","cellElement","contentSpan","currentValue","editor","createCellEditor","originalContent","style","display","editorContainer","document","createElement","appendChild","input","focus","select","dataset","setupEditorEvents","originalValue","editableOptions","createSelectEditor","createSwitchEditor","createTextareaEditor","createTextEditor","inputType","escapeHtml","rows","optionsArray","optionsHtml","option","checked","saveBtn","cancelBtn","addEventListener","e","preventDefault","saveCellEdit","cancelCellEdit","autoSave","newValue","oldValue","save","exitEditMode","setTimeout","remove","displayValue","dataFormatter","pipe","delete","text","div","textContent","addClass","selectCell","deselect","removeClass","LOOKUPS","exact","description","in","not","not_in","gt","gte","lt","lte","contains","icontains","startswith","istartswith","endswith","iendswith","isnull","val","range","parseFilterKey","paramKey","field","lookup","parts","split","possibleLookup","slice","formatFilterDisplay","lookupDef","hasStart","start","hasEnd","end","JSON","stringify","valueStr","String","values","v","trim","formattedValues","DjangoLookups","getLookupDescription","isValidLookup","hasOwnProperty","getAvailableLookups","Object","keys","buildFilterKey","TableView","ListView","selectionMode","selectable","emptyMessage","addButtonIcon","isFullscreen","searchable","sortable","filterable","paginated","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","exportOptions","showExport","exportSource","filters","additionalFilters","hideActivePills","hideActivePillNames","batchBarLocation","addButtonLabel","toolbarButtons","tableOptions","striped","bordered","hover","responsive","searchPlacement","searchPlaceholder","initializeColumns","extractColumnFilters","footerTotalColumns","footer_total","hasFooterTotals","buildTableTemplate","setupCollectionListeners","collection","on","updateFooterTotals","charAt","toUpperCase","parseColumnKey","fieldKey","totals","calculateFooterTotals","totalColumnIndex","safeKey","formatValue","sum","numValue","parseFloat","originalKey","batchPanelTop","buildBatchActionsPanel","batchPanelBottom","buildToolbarTemplate","__fs","fontSize","__val","buildTableClasses","buildTableHeaderTemplate","buildTableFooterTemplate","buildPaginationTemplate","background","buildActionButtonsTemplate","buildFilterDropdownTemplate","buildSearchTemplate","buttons","isFullscreenSupported","showAdd","dropdownItems","opt","button","handler","variant","checkPermissions","iconHtml","labelHtml","dataAttrs","btnClass","buildFilterList","allFilters","getAllAvailableFilters","activeFilters","getActiveFilters","isActive","activeClass","getFilterIcon","config","updateFilterPills","container","pillsHTML","buildActivePills","updateSearchInputs","searchInputs","querySelectorAll","hasSearch","search","toString","filterEntries","entries","getFilterLabel","headerCells","currentSort","getSortBy","getSortDirection","sortIcon","getSortIcon","responsiveClasses","footerCells","actionsHTML","batchPanelTitle","_createItemView","itemTemplate","containerId","itemViews","set","_onItemSelect","updateBatchActionsPanel","_onItemDeselect","_onRowClick","bind","_onRowView","_onRowEdit","_onRowDelete","_onCellEdit","_onCellSave","_onCellCancel","onMounted","setupSearchClearListener","onActionClearSearch","onRowClick","getModelClass","getModelName","MODEL_NAME","replace","getItemViewClass","VIEW_CLASS","getAddFormConfig","getEditFormConfig","getFormDialogConfig","FORM_DIALOG_CONFIG","renderTemplateString","Mustache","render","onItemView","ViewClass","viewInstance","Dialog","showDialog","header","body","centered","showData","onItemEdit","formConfig","result","showModelForm","success","status","showError","message","FormView","formFields","resp","refresh","onItemDelete","DELETE_TEMPLATE","confirm","confirmText","confirmClass","destroy","fullscreenEnabled","mozFullScreenEnabled","webkitFullscreenEnabled","msFullscreenEnabled","onActionToggleFullscreen","exitFullscreen","enterFullscreen","requestFullscreen","mozRequestFullScreen","webkitRequestFullscreen","msRequestFullscreen","updateFullscreenButton","setupFullscreenListeners","mozCancelFullScreen","webkitExitFullscreen","msExitFullscreen","_fullscreenHandler","handleFullscreenChange","fullscreenElement","mozFullScreenElement","webkitFullscreenElement","msFullscreenElement","cleanupFullscreenListeners","removeEventListener","onActionRefresh","onActionAdd","onAdd","showForm","addRequiresActiveGroup","group","getApp","activeGroup","addRequiresActiveUser","user","activeUser","addFormDefaults","assign","onActionExport","source","download","onExport","toJSON","onActionApplySearch","searchTerm","setFilter","params","restEnabled","sort","startsWith","direction","onActionSort","newSort","setParams","desc","sortField","a","b","aVal","bVal","updateSortIcons","currentSortField","currentSortDir","dropdown","isSorted","dropdownMenu","nextElementSibling","ascItem","descItem","noneItem","toggle","onActionSelectAll","isCurrentlyAllSelected","from","every","item","clearSelection","forEachItem","selectAllCell","onBeforeRender","searchValue","footerTotals","total","meta","count","Math","min","startEl","endEl","totalEl","pageSizeSelect","renderPagination","paginationContainer","currentPage","floor","totalPages","ceil","prevPage","nextPage","pages","visibleSet","i","visible","last","onActionPage","rawPage","parseInt","max","page","isNaN","onChangePageSize","newSize","allParams","processedKeys","filterDef","startName","endName","fieldName","inKey","filterConfig","getFilterConfig","filterKey","f","additionalFilter","getFilterDisplayValue","date","daterange","number","boolean","onActionAddFilter","buildFilterDialogField","newFilterValue","extractFilterValue","applyFilters","placeHolder","displayFormat","normalizeDateValue","Date","toISOString","str","test","num","Number","ms","startDate","begin","endDate","to","finish","valueArray","formResult","filter_value","onActionEditFilter","formData","onActionRemoveFilter","onActionClearAllFilters","selectedCount","getSelectedItems","panel","countEl","allSelected","someSelected","onActionBatch","batchAction","selectedItems","items","onActionClearSelection","onActionCustomToolbarButton","buttonIndex","call"],"mappings":"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"}
|