Files
eveAI/eveai_app/templates/eveai_list_view.html

505 lines
24 KiB
HTML

<style>
button.disabled, button:disabled {
opacity: 0.65;
cursor: not-allowed;
pointer-events: none;
}
</style>
<div class="container">
<input type="hidden" id="{{ table_id }}-selected-row" name="selected_row" value="">
<input type="hidden" id="{{ table_id }}-action" name="action" value="">
<div id="{{ table_id }}" class="tabulator-list-view"></div>
<div class="row mt-3">
{% set right_actions = actions|selectattr('position', 'equalto', 'right')|list %}
<div class="{% if right_actions %}col{% else %}col-12{% endif %}">
{% for action in actions if action.position != 'right' %}
<button type="button"
onclick="handleListViewAction('{{ action.value }}', {{ action.requiresSelection|tojson }}, event)"
class="btn {{ action.class|default('btn-primary') }} me-2 {% if action.requiresSelection %}requires-selection{% endif %}"
{% if action.requiresSelection %}disabled{% endif %}>
{{ action.text }}
</button>
{% endfor %}
</div>
{% if right_actions %}
<div class="col-auto text-end">
{% for action in actions if action.position == 'right' %}
<button type="button"
onclick="handleListViewAction('{{ action.value }}', {{ action.requiresSelection|tojson }}, event)"
class="btn {{ action.class|default('btn-primary') }} ms-2 {% if action.requiresSelection %}requires-selection{% endif %}"
{% if action.requiresSelection %}disabled{% endif %}>
{{ action.text }}
</button>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
// Zorg ervoor dat de ListView-module beschikbaar is
window.EveAI = window.EveAI || {};
window.EveAI.ListView = window.EveAI.ListView || {};
/* ListView-module functionaliteit:
* - Initialiseert een Tabulator tabel met de gegeven configuratie
* - Beheert rijselectie (maximaal 1 rij tegelijk)
* - Zorgt ervoor dat knoppen met requiresSelection=true inactief zijn wanneer er geen rij is geselecteerd
* - Ondersteunt verschillende Tabulator versies (5.x en 6.x)
*/
// Direct alle buttons met requiresSelection uitschakelen bij laden pagina
document.querySelectorAll('button[onclick*="handleListViewAction"]').forEach(button => {
const onclickAttr = button.getAttribute('onclick');
const match = onclickAttr.match(/handleListViewAction\('([^']+)',\s*(true|false)\)/i);
if (match && match[2].toLowerCase() === 'true') {
button.disabled = true;
button.classList.add('disabled');
}
})
// Voeg de benodigde functies toe als ze nog niet bestaan
if (!window.EveAI.ListView.initialize) {
window.EveAI.ListView.instances = {};
// Initialize functie
window.EveAI.ListView.initialize = function(containerId, options = {}) {
const container = document.getElementById(containerId);
if (!container) {
console.error(`Container ${containerId} not found`);
return null;
}
// Default configuration
const config = {
data: [],
columns: [],
initialSort: [],
initialFilters: [],
filterableColumns: [],
selectable: true,
actions: [],
usePagination: true,
tableHeight: 700,
...options
};
// Check if Tabulator is available
if (typeof window.Tabulator !== 'function') {
console.error('Tabulator not loaded (window.Tabulator missing).');
container.innerHTML = `<div class="alert alert-danger">
<strong>Error:</strong> Tabulator not loaded
</div>`;
return null;
}
// Register embedded handlers registry and custom formatters if not present
window.EveAI.ListView.embeddedHandlers = window.EveAI.ListView.embeddedHandlers || {};
window.EveAI.ListView.formatters = window.EveAI.ListView.formatters || {};
// Custom formatter: typeBadge (agent/task/tool)
window.EveAI.ListView.formatters.typeBadge = function(cell, formatterParams, onRendered) {
const val = (cell.getValue() || '').toString();
const map = {
'agent': { cls: 'badge bg-purple', label: 'Agent' },
'task': { cls: 'badge bg-orange', label: 'Task' },
'tool': { cls: 'badge bg-teal', label: 'Tool' },
};
const conf = map[val] || { cls: 'badge bg-secondary', label: val };
return `<span class="${conf.cls}">${conf.label}</span>`;
};
try {
// Create Tabulator table
const tableContainer = document.createElement('div');
tableContainer.className = 'tabulator-container tabulator-list-view mt-3';
container.appendChild(tableContainer);
// Basisinstellingen voor tabel configuratie
const tableConfig = {
data: config.data,
columns: this._buildColumns(config.columns, config.selectable),
layout: "fitColumns",
// Conditionele paginatie of progressieve lading
...(config.usePagination ? {
pagination: "local",
paginationSize: 25,
paginationSizeSelector: [10, 25, 50, 100]
} : {
progressiveLoad: "scroll" // Gebruik progressieve lading voor grote datasets wanneer paginatie uitstaat
}),
// Gemeenschappelijke configuratie voor beide modi
movableColumns: true,
resizableRows: false, // Schakel dit uit om prestatieproblemen te voorkomen
initialSort: config.initialSort,
initialFilter: config.initialFilters,
selectable: config.selectable ? 1 : false, // Beperk tot maximaal 1 rij selectie of schakel uit
selectableRangeMode: "click",
selectableCheck: function() { return true; }, // Zorg ervoor dat alle cellen selecteerbaar zijn
rowClick: function(e, row) {
// Selecteer de rij bij klikken op willekeurige cel
if (config.selectable) {
console.log('Row clicked!', row.getData());
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen als extra maatregel
const instance = EveAI.ListView.instances[containerId];
if (instance) {
// Expliciet de geselecteerde rij instellen
instance.selectedRow = row.getData();
// Direct UI updaten voor betere gebruikerservaring
console.log('Updating buttons from rowClick handler');
EveAI.ListView._updateActionButtons(containerId);
}
}
},
cellClick: function(e, cell) {
// Gebruik ook cellClick om ervoor te zorgen dat klikken op een cel de rij selecteert
if (config.selectable && !e.target.matches('a, input, button, select, .tabulator-cell-editing')) {
console.log('Cell clicked!', cell.getData());
const row = cell.getRow();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen als extra maatregel
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
// Direct forceer een update van de buttons - ook als de rowSelectionChanged niet triggert
console.log('Updating buttons from cellClick handler');
setTimeout(() => EveAI.ListView._updateActionButtons(containerId), 0);
}
}
},
cellTap: function(e, cell) {
// Extra handler voor touch devices
if (config.selectable && !e.target.matches('a, input, button, select, .tabulator-cell-editing')) {
console.log('Cell tapped!', cell.getData());
const row = cell.getRow();
row.getTable().deselectRow();
row.select();
// Handmatig de rowSelectionChanged event aanroepen
const instance = EveAI.ListView.instances[containerId];
if (instance) {
instance.selectedRow = row.getData();
EveAI.ListView._updateActionButtons(containerId);
}
}
},
rowSelectionChanged: function(data, rows) {
console.log("Aantal geselecteerde rijen:", rows.length);
console.log("Geselecteerde rijen:", rows.map(r => r.getData()));
// Update de geselecteerde rij (enkelvoud)
const selectedData = rows.length > 0 ? rows[0].getData() : null;
console.log('rowSelectionChanged met data:', selectedData);
// Controleer of de instance bestaat
if (EveAI.ListView.instances[containerId]) {
EveAI.ListView.instances[containerId].selectedRow = selectedData;
// Met kleine timeout om zeker te zijn dat alles is bijgewerkt
setTimeout(() => {
console.log('Updating buttons from rowSelectionChanged handler');
EveAI.ListView._updateActionButtons(containerId);
}, 10);
} else {
console.warn(`Instance voor ${containerId} niet gevonden in rowSelectionChanged`);
}
},
rowFormatter: function(row) {
// Voeg cursor-style toe om aan te geven dat rijen klikbaar zijn
if (config.selectable) {
row.getElement().style.cursor = 'pointer';
}
},
placeholder: "No data available",
tooltips: false, // Schakel tooltips uit voor betere prestaties
responsiveLayout: false, // Schakel responsiveLayout uit om recursieve problemen te voorkomen
renderVerticalBuffer: 20, // Optimaliseer virtuele rendering
virtualDomBuffer: 80, // Optimaliseer virtuele DOM buffer
height: config.tableHeight // Gebruik de geconfigureerde tabel hoogte
};
// Maak Tabulator instantie met de geconfigureerde instellingen
const table = new Tabulator(tableContainer, tableConfig);
// Store instance - maar maak GEEN action buttons meer in JavaScript
this.instances[containerId] = {
table: table,
config: config,
selectedRow: null
};
// Direct bij initialisatie: zorg ervoor dat knoppen met requiresSelection=True inactief zijn
// Gebruik een groter timeout om te zorgen dat alle componenten zijn geladen
setTimeout(() => {
console.log('Initialiseren van action buttons...');
this._updateActionButtons(containerId);
}, 100);
// Registreer events met de Tabulator API voor betere compatibiliteit
table.on("rowClick", function(e, row){
console.log("Tabulator API: Row clicked!", row.getData());
if (config.selectable) {
// Stop event propagation om te voorkomen dat andere handlers interfereren
e.stopPropagation();
row.getTable().deselectRow();
row.select();
}
});
// Row selection event met de Tabulator API
table.on("rowSelectionChanged", function(data, rows){
console.log("Tabulator API: Aantal geselecteerde rijen:", rows.length);
console.log("Tabulator API: Geselecteerde rijen:", rows.map(r => r.getData()));
// Update de geselecteerde rij (enkelvoud)
EveAI.ListView.instances[containerId].selectedRow = rows.length > 0 ? rows[0].getData() : null;
EveAI.ListView._updateActionButtons(containerId);
});
return table;
} catch (error) {
console.error('Error initializing ListView:', error);
container.innerHTML = `<div class="alert alert-danger">
<strong>Error loading data:</strong> ${error.message}
</div>`;
return null;
}
};
// Bouw kolommen functie
window.EveAI.ListView._buildColumns = function(columns, selectable) {
const tabulatorColumns = [];
// Detecteer Tabulator versie (6.x heeft een andere aanpak voor column definities)
const isTabulator6Plus = typeof Tabulator === 'function' && Tabulator.version &&
parseInt(Tabulator.version.split('.')[0]) >= 6;
console.log('Tabulator versie detectie:', isTabulator6Plus ? '6+' : 'Pre-6');
// Add data columns
columns.forEach(col => {
// Maak een nieuwe schone kolom met alleen geldige Tabulator-eigenschappen
// voor de gedetecteerde versie
const column = {
title: col.title,
field: col.field,
width: col.width,
hozAlign: col.hozAlign || 'left',
vertAlign: col.vertAlign || 'middle'
};
// Voeg sorteren toe volgens de juiste manier per versie
if (isTabulator6Plus) {
// Tabulator 6+ gebruikt sorteerbaarheid via kolom-opties
column.sorter = col.field === 'id' || col.field.endsWith('_id') || col.type === 'number' ?
'number' : 'string';
column.sorterParams = {};
column.sorteringActive = col.sortable !== false;
} else {
// Pre-6 versies gebruiken headerSort
column.headerSort = col.sortable !== false;
// Zorg voor juiste numerieke sortering voor ID velden
if (col.field === 'id' || col.field.endsWith('_id') || col.type === 'number') {
column.sorter = 'number';
}
}
// Voeg formattering toe volgens de juiste manier per versie
if (col.formatter) {
// Resolve custom formatter name from registry when provided as string
let fmt = col.formatter;
if (typeof fmt === 'string' && window.EveAI && window.EveAI.ListView && window.EveAI.ListView.formatters && window.EveAI.ListView.formatters[fmt]) {
fmt = window.EveAI.ListView.formatters[fmt];
}
// Apply formatter for both Tabulator 5 and 6
column.formatter = fmt;
}
// Voeg filtering toe volgens de juiste manier per versie
if (isTabulator6Plus) {
// Tabulator 6+ gebruikt verschillende eigenschappen voor filtering
column.filterable = col.filterable !== false;
column.filterParams = {};
if (col.type === 'date') {
column.filterParams.type = 'date';
} else if (col.type === 'number') {
column.filterParams.type = 'number';
} else if (col.filterValues) {
column.filterParams.values = col.filterValues;
}
} else {
// Pre-6 versies gebruiken headerFilter
column.headerFilter = col.filterable !== false ? 'input' : false;
// Set appropriate header filter based on the data type
if (col.type === 'date') {
column.headerFilter = "input";
column.headerFilterParams = {type: "date"};
} else if (col.type === 'number') {
column.headerFilter = "number";
} else if (col.filterValues) {
column.headerFilter = "select";
column.headerFilterParams = {values: col.filterValues};
}
}
tabulatorColumns.push(column);
});
return tabulatorColumns;
};
// Update action buttons functie
window.EveAI.ListView._updateActionButtons = function(containerId) {
const instance = this.instances[containerId];
if (!instance) {
console.warn(`Kan buttons niet updaten: geen instance voor ${containerId}`);
return;
}
// Zoek buttons in de volledige formulier context (niet alleen de container)
const form = document.getElementById(`${containerId}-form`) || document.querySelector('form');
const buttons = form ? form.querySelectorAll('button[onclick*="handleListViewAction"]') :
document.querySelectorAll('button[onclick*="handleListViewAction"]');
console.log(`Updating buttons voor ${containerId}, ${buttons.length} buttons gevonden, selectedRow:`, instance.selectedRow);
buttons.forEach(button => {
// Parse the onclick attribute to get the action value and requiresSelection parameter
const onclickAttr = button.getAttribute('onclick');
const match = onclickAttr.match(/handleListViewAction\('([^']+)',\s*(true|false)\)/i);
if (match) {
const actionValue = match[1];
const requiresSelection = match[2].toLowerCase() === 'true';
// Direct toepassen van requiresSelection-check
if (requiresSelection) {
// Controleer of er een geselecteerde rij is
const isDisabled = !instance.selectedRow;
button.disabled = isDisabled;
// Voeg/verwijder disabled class voor styling
if (isDisabled) {
button.classList.add('disabled');
} else {
button.classList.remove('disabled');
}
console.log(`Button ${actionValue} updated: disabled=${isDisabled}`);
}
// Backup check op basis van actions in config (voor achterwaartse compatibiliteit)
const action = instance.config.actions.find(a => a.value === actionValue);
if (action && action.requiresSelection === true && !requiresSelection) {
// Ook controleren op basis van action config
const isDisabled = !instance.selectedRow;
button.disabled = isDisabled;
// Voeg/verwijder disabled class voor styling
if (isDisabled) {
button.classList.add('disabled');
} else {
button.classList.remove('disabled');
}
}
}
});
// Update hidden input with selected row data
this._updateSelectedRowInput(containerId);
};
// Update selected row input functie
window.EveAI.ListView._updateSelectedRowInput = function(containerId) {
const instance = this.instances[containerId];
let hiddenInput = document.getElementById(`${containerId}-selected-row`);
if (!hiddenInput) {
hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'selected_row';
hiddenInput.id = `${containerId}-selected-row`;
document.getElementById(containerId).appendChild(hiddenInput);
}
if (instance.selectedRow) {
// Bewaar de geselecteerde rij-ID
hiddenInput.value = JSON.stringify({value: instance.selectedRow.id});
} else {
hiddenInput.value = '';
}
};
}
// Definieer de handleListViewAction functie als deze nog niet bestaat
if (typeof window.handleListViewAction !== 'function') {
window.handleListViewAction = function(action, requiresSelection, e) {
// Prefer embedded handler when available (embedded mode)
const evt = e || window.event;
const target = evt?.target || evt?.srcElement;
// Determine tableId scoped to the closest container of this partial
let tableId = null;
if (target) {
const containerEl = target.closest('.container');
const scopedTable = containerEl ? containerEl.querySelector('.tabulator-list-view') : null;
tableId = scopedTable ? scopedTable.id : null;
}
if (!tableId) {
// fallback to previous behavior
const form = target ? target.closest('form') : null;
tableId = form ? form.id.replace('-form', '') : document.querySelector('.tabulator-list-view')?.id;
}
if (!tableId) {
console.error('Kan tableId niet bepalen voor action:', action);
return false;
}
// Enforce selection when required
if (requiresSelection === true) {
const instance = window.EveAI.ListView.instances[tableId];
if (!instance || !instance.selectedRow) {
alert('Selecteer eerst een item uit de lijst.');
return false;
}
}
// Embedded handler first
const embedded = window.EveAI.ListView.embeddedHandlers && window.EveAI.ListView.embeddedHandlers[tableId];
if (typeof embedded === 'function') {
const instance = window.EveAI.ListView.instances[tableId];
try {
embedded(action, instance ? instance.selectedRow : null, tableId);
return true;
} catch (err) {
console.error('Embedded handler error:', err);
return false;
}
}
// Fallback to global handler (legacy behavior)
if (window.EveAI && window.EveAI.ListView && typeof window.EveAI.ListView.handleAction === 'function') {
return window.EveAI.ListView.handleAction(action, requiresSelection, tableId);
}
// Final fallback to form submit (legacy)
const actionInput = document.getElementById(`${tableId}-action`);
if (actionInput) actionInput.value = action;
const form = document.getElementById(`${tableId}-form`);
if (form) { form.submit(); return true; }
return false;
};
}
console.log('EveAI List View component geladen');
});
</script>