385 lines
14 KiB
JavaScript
385 lines
14 KiB
JavaScript
class ColumnManager {
|
|
constructor(tableId, options = {}) {
|
|
this.tableId = tableId;
|
|
this.table = document.getElementById(tableId);
|
|
if (!this.table) return;
|
|
|
|
this.options = Object.assign({
|
|
entityType: 'individui',
|
|
storageKey: null,
|
|
resizable: true,
|
|
reorderable: true,
|
|
persistWidths: true,
|
|
persistOrder: true,
|
|
}, options);
|
|
|
|
this.columns = [];
|
|
this.columnWidths = {};
|
|
this.columnOrder = [];
|
|
this.dragState = null;
|
|
this.resizeState = null;
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.loadState();
|
|
this.initColumns();
|
|
if (this.options.resizable) this.initResize();
|
|
if (this.options.reorderable) this.initReorder();
|
|
this.applyState();
|
|
}
|
|
|
|
initColumns() {
|
|
const ths = this.table.querySelectorAll('thead th');
|
|
ths.forEach((th, idx) => {
|
|
const colKey = th.dataset.column || `col_${idx}`;
|
|
const col = {
|
|
index: idx,
|
|
key: colKey,
|
|
element: th,
|
|
width: th.style.width || '',
|
|
visible: th.style.display !== 'none',
|
|
label: th.textContent.trim(),
|
|
};
|
|
this.columns.push(col);
|
|
});
|
|
}
|
|
|
|
loadState() {
|
|
if (this.options.storageKey) {
|
|
try {
|
|
const saved = sessionStorage.getItem(this.options.storageKey);
|
|
if (saved) {
|
|
const data = JSON.parse(saved);
|
|
this.columnWidths = data.widths || {};
|
|
this.columnOrder = data.order || [];
|
|
return;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
const vistaEl = document.getElementById('vista-data');
|
|
if (vistaEl && vistaEl.textContent && vistaEl.textContent !== 'null') {
|
|
try {
|
|
const vista = JSON.parse(vistaEl.textContent);
|
|
this.columnWidths = vista.colonne_larghezze || {};
|
|
this.columnOrder = vista.colonne_ordine || [];
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
persistState() {
|
|
if (!this.options.storageKey) return;
|
|
try {
|
|
sessionStorage.setItem(this.options.storageKey, JSON.stringify({
|
|
widths: this.columnWidths,
|
|
order: this.columnOrder,
|
|
}));
|
|
} catch (e) {}
|
|
}
|
|
|
|
applyState() {
|
|
if (this.columnOrder.length > 0) {
|
|
this.reorderColumns(this.columnOrder, false);
|
|
}
|
|
if (Object.keys(this.columnWidths).length > 0) {
|
|
this.applyWidths();
|
|
}
|
|
}
|
|
|
|
applyWidths() {
|
|
this.columns.forEach(col => {
|
|
const w = this.columnWidths[col.key];
|
|
if (w) {
|
|
col.element.style.width = w;
|
|
}
|
|
});
|
|
}
|
|
|
|
initResize() {
|
|
this.table.querySelectorAll('thead th').forEach(th => {
|
|
const handle = document.createElement('div');
|
|
handle.className = 'col-resize-handle';
|
|
handle.style.cssText = 'position:absolute;right:0;top:0;bottom:0;width:5px;cursor:col-resize;z-index:10;';
|
|
th.style.position = 'relative';
|
|
th.appendChild(handle);
|
|
|
|
handle.addEventListener('mousedown', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const thEl = th;
|
|
const startX = e.clientX;
|
|
const startWidth = thEl.offsetWidth;
|
|
|
|
const onMouseMove = (ev) => {
|
|
const diff = ev.clientX - startX;
|
|
const newWidth = Math.max(30, startWidth + diff);
|
|
thEl.style.width = newWidth + 'px';
|
|
const colKey = thEl.dataset.column;
|
|
if (colKey) {
|
|
this.columnWidths[colKey] = newWidth + 'px';
|
|
this.persistState();
|
|
}
|
|
};
|
|
|
|
const onMouseUp = () => {
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
document.body.style.cursor = '';
|
|
document.body.style.userSelect = '';
|
|
};
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
document.body.style.cursor = 'col-resize';
|
|
document.body.style.userSelect = 'none';
|
|
});
|
|
});
|
|
}
|
|
|
|
initReorder() {
|
|
const thead = this.table.querySelector('thead');
|
|
if (!thead) return;
|
|
|
|
let dragCol = null;
|
|
let dragIndex = -1;
|
|
let placeholder = null;
|
|
|
|
thead.querySelectorAll('th').forEach(th => {
|
|
if (th.querySelector('.col-resize-handle')) return;
|
|
th.draggable = true;
|
|
|
|
th.addEventListener('dragstart', (e) => {
|
|
dragCol = th;
|
|
dragIndex = Array.from(thead.querySelectorAll('th')).indexOf(th);
|
|
e.dataTransfer.effectAllowed = 'move';
|
|
e.dataTransfer.setData('text/plain', '');
|
|
th.classList.add('dragging');
|
|
});
|
|
|
|
th.addEventListener('dragend', () => {
|
|
th.classList.remove('dragging');
|
|
if (placeholder && placeholder.parentNode) {
|
|
placeholder.parentNode.removeChild(placeholder);
|
|
}
|
|
dragCol = null;
|
|
dragIndex = -1;
|
|
placeholder = null;
|
|
});
|
|
|
|
th.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = 'move';
|
|
if (!dragCol || dragCol === th) return;
|
|
|
|
if (!placeholder) {
|
|
placeholder = document.createElement('th');
|
|
placeholder.style.cssText = 'border:2px dashed #007bff;background:rgba(0,123,255,0.1);width:0;padding:0;';
|
|
dragCol.parentNode.insertBefore(placeholder, dragCol);
|
|
}
|
|
|
|
const rect = th.getBoundingClientRect();
|
|
const midX = rect.left + rect.width / 2;
|
|
const parent = th.parentNode;
|
|
|
|
if (e.clientX < midX) {
|
|
parent.insertBefore(placeholder, th);
|
|
} else {
|
|
parent.insertBefore(placeholder, th.nextSibling);
|
|
}
|
|
});
|
|
|
|
th.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
if (!dragCol || dragCol === th) return;
|
|
|
|
const parent = th.parentNode;
|
|
const allTh = Array.from(parent.querySelectorAll('th')).filter(el => el !== placeholder);
|
|
|
|
const fromIdx = allTh.indexOf(dragCol);
|
|
let toIdx = allTh.indexOf(th);
|
|
|
|
if (placeholder) {
|
|
toIdx = Array.from(parent.children).indexOf(placeholder);
|
|
if (placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
|
|
placeholder = null;
|
|
}
|
|
|
|
if (fromIdx < 0) return;
|
|
|
|
const actualRows = this.table.querySelectorAll('tr');
|
|
actualRows.forEach(row => {
|
|
const cells = row.querySelectorAll('td, th');
|
|
if (cells[fromIdx] && cells[toIdx]) {
|
|
if (toIdx > fromIdx) {
|
|
row.insertBefore(cells[fromIdx], cells[toIdx].nextSibling);
|
|
} else {
|
|
row.insertBefore(cells[fromIdx], cells[toIdx]);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.updateColumnOrder();
|
|
});
|
|
});
|
|
}
|
|
|
|
updateColumnOrder() {
|
|
const ths = this.table.querySelectorAll('thead th');
|
|
this.columnOrder = [];
|
|
const keysInOrder = [];
|
|
ths.forEach((th, idx) => {
|
|
const colKey = th.dataset.column || `col_${idx}`;
|
|
keysInOrder.push(colKey);
|
|
this.columnOrder.push(colKey);
|
|
});
|
|
this.columns.sort((a, b) => keysInOrder.indexOf(a.key) - keysInOrder.indexOf(b.key));
|
|
this.columns.forEach((col, idx) => col.index = idx);
|
|
this.persistState();
|
|
}
|
|
|
|
reorderColumns(keyOrder, persist = true) {
|
|
if (!keyOrder || keyOrder.length === 0) return;
|
|
const thead = this.table.querySelector('thead');
|
|
if (!thead) return;
|
|
|
|
const rows = this.table.querySelectorAll('tr');
|
|
const keyToThIndex = {};
|
|
const allTh = Array.from(thead.querySelectorAll('th'));
|
|
|
|
allTh.forEach((th, idx) => {
|
|
const key = th.dataset.column || `col_${idx}`;
|
|
keyToThIndex[key] = idx;
|
|
});
|
|
|
|
rows.forEach(row => {
|
|
const cells = row.querySelectorAll('td, th');
|
|
const reordered = [];
|
|
let lastRef = null;
|
|
|
|
keyOrder.forEach(key => {
|
|
const srcIdx = keyToThIndex[key];
|
|
if (srcIdx !== undefined && cells[srcIdx]) {
|
|
reordered.push(cells[srcIdx]);
|
|
}
|
|
});
|
|
|
|
reordered.forEach(cell => {
|
|
if (lastRef) {
|
|
row.insertBefore(cell, lastRef.nextSibling);
|
|
} else {
|
|
row.insertBefore(cell, row.firstChild);
|
|
}
|
|
lastRef = cell;
|
|
});
|
|
});
|
|
|
|
this.columnOrder = [...keyOrder];
|
|
this.columns.sort((a, b) => keyOrder.indexOf(a.key) - keyOrder.indexOf(b.key));
|
|
this.columns.forEach((col, idx) => col.index = idx);
|
|
|
|
if (persist) this.persistState();
|
|
}
|
|
|
|
setVisible(columnKey, visible) {
|
|
const th = this.table.querySelector(`th[data-column="${columnKey}"]`);
|
|
if (th) th.style.display = visible ? '' : 'none';
|
|
|
|
const colIdx = this.columns.findIndex(c => c.key === columnKey);
|
|
if (colIdx >= 0) {
|
|
this.columns[colIdx].visible = visible;
|
|
const rows = this.table.querySelectorAll('tbody tr, thead tr');
|
|
rows.forEach(row => {
|
|
const cells = row.querySelectorAll('td, th');
|
|
if (cells[colIdx]) {
|
|
cells[colIdx].style.display = visible ? '' : 'none';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
getColumnWidths() {
|
|
return { ...this.columnWidths };
|
|
}
|
|
|
|
getColumnOrder() {
|
|
return [...this.columnOrder];
|
|
}
|
|
|
|
getVisibleColumns() {
|
|
return this.columns.filter(c => c.visible).map(c => c.key);
|
|
}
|
|
|
|
getColumnMetadata() {
|
|
return {
|
|
larghezze: this.getColumnWidths(),
|
|
ordine: this.getColumnOrder(),
|
|
visibili: this.getVisibleColumns(),
|
|
};
|
|
}
|
|
|
|
printWithSettings() {
|
|
const printWindow = window.open('', '_blank');
|
|
if (!printWindow) return;
|
|
|
|
const styles = Array.from(document.styleSheets)
|
|
.filter(sheet => {
|
|
try { return sheet.cssRules; } catch (e) { return false; }
|
|
})
|
|
.map(sheet => Array.from(sheet.cssRules).map(rule => rule.cssText).join('\n'))
|
|
.join('\n');
|
|
|
|
const printStyles = `
|
|
@media print {
|
|
body { font-family: 'DejaVu Sans', Arial, sans-serif; font-size: 10pt; }
|
|
.no-print { display: none !important; }
|
|
.card { border: none !important; box-shadow: none !important; }
|
|
.table { border-collapse: collapse; width: 100%; }
|
|
.table th { background: #f5f5f5 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
.table th, .table td { border: 1px solid #ddd; padding: 4px 6px; }
|
|
a { text-decoration: none; color: #000; }
|
|
.badge { border: 1px solid #999; padding: 1px 4px; font-size: 8pt; }
|
|
.pagination, .card-header .card-tools { display: none !important; }
|
|
.page-break { page-break-before: always; }
|
|
}
|
|
`;
|
|
|
|
const title = document.title || 'Stampa';
|
|
const pageTitle = document.querySelector('.page_title, .card-title h1, h1')?.textContent?.trim() || title;
|
|
|
|
let content = '';
|
|
const printArea = this.table?.cloneNode(true);
|
|
if (printArea) {
|
|
printArea.querySelectorAll('.col-resize-handle').forEach(h => h.remove());
|
|
content = printArea.outerHTML;
|
|
} else {
|
|
content = document.getElementById(this.tableId)?.outerHTML || '';
|
|
}
|
|
|
|
printWindow.document.write(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>${title}</title>
|
|
<style>${styles}${printStyles}</style>
|
|
</head>
|
|
<body>
|
|
<div class="no-print" style="text-align:right;margin-bottom:10px;">
|
|
<button onclick="window.print()" style="padding:6px 16px;font-size:12pt;">🖨️ Stampa</button>
|
|
<button onclick="window.close()" style="padding:6px 16px;font-size:12pt;">✖ Chiudi</button>
|
|
</div>
|
|
<h2 style="margin-bottom:16px;">${pageTitle}</h2>
|
|
${content}
|
|
<p style="text-align:center;margin-top:16px;color:#999;font-size:8pt;">
|
|
Stampato il ${new Date().toLocaleDateString('it-IT')} — ${document.location.href}
|
|
</p>
|
|
</body>
|
|
</html>
|
|
`);
|
|
printWindow.document.close();
|
|
}
|
|
}
|
|
|
|
window.ColumnManager = ColumnManager;
|