Moin Volker,
um Dir etwas Arbeit abzunehmen, habe ich die HTML und CSS komplett neu geschrieben. Wenn ich die Fehler bereits lokalisiert habe, kann ich sie auch sogleich beheben.
In der HTML war ein Fehler bezüglich des
Table-Header-Tags. Nun ist auch mein Administratorbereich, mit importierter CSV-Datei richtig angezeigt.
Das
Drag & Drop in der Header-Zeile (Ziehen und Ablegen) der Spalten funktioniert korrekt.
Die Beschriftung vom
Button zum
↑ CSV Import habe ich nun auch mittig platziert.
Der rote Balken um das
Suchen-Feld kam vom "Behinderten-Fokus", den habe ich mit einem Schlagschatten ersetzt.
Die
Sortierpfeile haben nun eine korrekte CSS erhalten und sind auch am Handy richtig platziert und nicht hinter den Header-Namen.
Bitte schreibe mir, wenn Du die
.mbrext-Datei neu erstellt hast, sodass ich diese dann auch nochmals neu einlesen kann. Die Farben könntest Du noch anpassen oder so lassen, im Moment sind diese wie auf meiner Webseite:
https://www.mobirise-tutorials.com/VN-B ... ml#Tabelle
Ich habe bereits
sämtliche Korrekturen vorgenommen. Damit sind meine weiter oben gemachten Beiträge hinfällig.
HTML:
Code: Alles auswählen
<section data-bs-version="5.1" class="vn-table" group="Content" plugins="vn-table" data-endpoint="assets/vn-table/table.php" data-gid="{{tableId}}" data-page-length="{{pageLength}}" data-show-search="{{showSearch}}" data-show-sort="{{showSort}}" data-show-stripes="{{showStripes}}" data-show-hover="{{showHover}}" data-show-border="{{showBorder}}" data-mobiletrigger="{{mobileTrigger}}">
<mbr-parameters>
<header>Tabelle</header>
<input type="text" title="Tabellen-ID (eindeutig!)" name="tableId" value="tabelle-1">
<input type="range" inline title="Einträge pro Seite" name="pageLength" min="5" max="100" step="5" value="10">
<input type="checkbox" title="Suchfeld anzeigen" name="showSearch" checked>
<input type="checkbox" title="Sortierung erlauben" name="showSort" checked>
<input type="checkbox" title="Zeilenstreifen" name="showStripes" checked>
<input type="checkbox" title="Hover-Effekt" name="showHover" checked>
<input type="checkbox" title="Rahmen" name="showBorder">
<input type="checkbox" title="Volle Breite (Full Width)" name="fullWidth" checked>
<header>Handy Admin-Trigger</header>
<select title="Handy Admin-Trigger" name="mobileTrigger">
<option value="button">⚙️ Button</option>
<option value="tap4">4x Tippen</option>
<option value="shake" selected>Schütteln</option>
</select>
<header>Schriftgrößen</header>
<input type="range" inline title="Schrift Kopfzeile (px)" name="headFontSize" min="10" max="24" step="1" value="16">
<input type="range" inline title="Schrift Zeilen (px)" name="bodyFontSize" min="10" max="24" step="1" value="16">
<header>Abstände Größe</header>
<input type="range" inline title="Breite (%)" name="tableWidth" min="30" max="100" step="5" value="70">
<input type="range" inline title="Oben" name="paddingTop" min="2" max="12" step="1" value="6">
<input type="range" inline title="Unten" name="paddingBottom" min="2" max="12" step="1" value="6">
<input type="range" inline title="Ecken" name="tableRadius" min="0" max="20" step="2" value="20">
<header>Farben</header>
<input type="color" title="Seitenhintergrund" name="bgColor" value="#bed3f9">
<input type="color" title="Karten-Hintergrund" name="cardColor" value="#8caff0">
<input type="color" title="Kopfzeile Hintergrund" name="headBg" value="#1952b9">
<input type="color" title="Kopfzeile Text" name="headText" value="#ffffff">
<input type="color" title="Akzent" name="accentColor" value="#6592e6">
<input type="color" title="Text" name="textColor" value="#00276d">
<input type="color" title="Streifen" name="stripeColor" value="#9fdbf8">
<input type="color" title="Rahmen" name="borderColor" value="#ffffff">
<input type="color" title="Admin Textfarbe" name="adminTextColor" value="#000000">
</mbr-parameters>
<div mbr-class="{'container': !fullWidth, 'container-fluid': fullWidth}">
<div class="row justify-content-center">
<div class="col-12">
<div class="vt-card">
<div class="vt-loading" id="vt-loading">⟳ Tabelle wird geladen…</div>
<div id="vt-dummy">
<table style="width:100%;border-collapse:collapse;font-size:0.88rem;">
<thead>
<tr>
<th style="background:var(--vt-head-bg,#4479d9);color:var(--vt-head-text,#fff);padding:0.65rem 0.9rem;text-align:left;">Name</th>
<th style="background:var(--vt-head-bg,#4479d9);color:var(--vt-head-text,#fff);padding:0.65rem 0.9rem;text-align:left;">Beschreibung</th>
<th style="background:var(--vt-head-bg,#4479d9);color:var(--vt-head-text,#fff);padding:0.65rem 0.9rem;text-align:left;">Preis</th>
<th style="background:var(--vt-head-bg,#4479d9);color:var(--vt-head-text,#fff);padding:0.65rem 0.9rem;text-align:left;">Status</th>
</tr>
</thead>
<tbody>
<tr style="background:var(--vt-card,#fafafa);">
<td style="padding:0.55rem 0.9rem;color:var(--vt-text,#000);border-bottom:1px solid var(--vt-border,#e43f3f);">Max Mustermann</td>
<td style="padding:0.55rem 0.9rem;color:var(--vt-text,#000);border-bottom:1px solid var(--vt-border,#e43f3f);">Beispieleintrag Zeile 1</td>
<td style="padding:0.55rem 0.9rem;color:var(--vt-text,#000);border-bottom:1px solid var(--vt-border,#e43f3f);">9,99 €</td>
<td style="padding:0.55rem 0.9rem;color:var(--vt-accent,#1d9e75);border-bottom:1px solid var(--vt-border,#e43f3f);">✓ Aktiv</td>
</tr>
<tr style="background:var(--vt-stripe,#9fdbf8);">
<td style="padding:0.55rem 0.9rem;color:var(--vt-text,#000);border-bottom:1px solid var(--vt-border,#e43f3f);">Erika Musterfrau</td>
<td style="padding:0.55rem 0.9rem;color:var(--vt-text,#000);border-bottom:1px solid var(--vt-border,#e43f3f);">Beispieleintrag Zeile 2</td>
<td style="padding:0.55rem 0.9rem;color:var(--vt-text,#000);border-bottom:1px solid var(--vt-border,#e43f3f);">19,99 €</td>
<td style="padding:0.55rem 0.9rem;color:var(--vt-accent,#1d9e75);border-bottom:1px solid var(--vt-border,#e43f3f);">✓ Aktiv</td>
</tr>
</tbody>
</table>
<div style="margin-top:0.75rem;padding:0.5rem 0.75rem;background:#e8f5e9;border:1px solid #1d9e75;border-radius:6px;text-align:center;">
<span style="color:#1d9e75;font-size:0.75rem;">📊 Vorschau zum Stylen – echte Daten erscheinen nach dem Publish</span>
</div>
</div>
<div id="vt-view" style="display:none;"></div>
<div id="vt-admin" style="display:none;"></div>
</div>
</div>
</div>
</div>
<div class="vt-modal" id="vt-modal" style="display:none;">
<div class="vt-modal-box">
<div class="vt-modal-hd">
<h3 id="vt-modal-title">🔐 Tabellen-Admin</h3>
<button
onclick="document.getElementById('vt-modal').style.display='none'"
style="background:none;border:none;color:#555;font-size:1.1rem;cursor:pointer;"
>✕</button>
</div>
<div id="vt-setup-wrap" style="display:none;">
<input
type="password"
id="vt-setup-pw"
class="vt-input"
placeholder="Passwort (min. 4 Zeichen)"
autocomplete="new-password"
>
<input
type="password"
id="vt-setup-pw2"
class="vt-input"
placeholder="Bestätigen"
style="margin-top:0.5rem;"
autocomplete="new-password"
>
</div>
<div id="vt-login-wrap">
<input
type="password"
id="vt-pw"
class="vt-input"
placeholder="Admin-Passwort"
autocomplete="current-password"
>
</div>
<div class="vt-err" id="vt-err" style="display:none;"></div>
<div style="display:flex;gap:0.5rem;margin-top:0.75rem;">
<button class="vt-btn-primary" id="vt-login-btn">Anmelden</button>
<button
class="vt-btn-ghost"
onclick="document.getElementById('vt-modal').style.display='none'"
>Abbrechen</button>
</div>
</div>
</div>
<div
id="vt-toast"
style="display:none;position:fixed;bottom:1.5rem;right:1.5rem;color:#fff;padding:0.75rem 1.25rem;border-radius:8px;z-index:99999;font-size:0.88rem;font-weight:600;"></div>
<script>
(function(){
var inBuilder =
document.querySelector('html').classList.contains('is-builder')
|| window.location.port === '409'
|| window.location.protocol === 'file:';
var sec = document.querySelector('section.vn-table');
if (!sec) return;
if (!inBuilder) {
var dummy = document.getElementById('vt-dummy');
if (dummy) dummy.remove();
} else {
var ld = document.getElementById('vt-loading');
if (ld) ld.style.display = 'none';
return;
}
// Endpoint-Pfad, funktioniert auch aus Unterordnern
var rawEp = (sec.dataset.endpoint || 'assets/vn-table/table.php').trim();
var ep = (function(){
var link = document.querySelector('link[href*="assets/"]');
if (link) {
return link.getAttribute('href').split('assets/')[0]
+ 'assets/vn-table/table.php';
}
return rawEp;
})();
var gid = sec.dataset.gid || 'tabelle-1';
// Checkbox-Werte korrekt lesen
var pgLen = parseInt(sec.dataset.pageLength, 10) || 35;
var doSearch =
sec.dataset.showSearch === 'true'
|| sec.dataset.showSearch === '1';
var doSort =
sec.dataset.showSort === 'true'
|| sec.dataset.showSort === '1';
var doStripes =
sec.dataset.showStripes === 'true'
|| sec.dataset.showStripes === '1';
var doHover =
sec.dataset.showHover === 'true'
|| sec.dataset.showHover === '1';
var doBorder =
sec.dataset.showBorder === 'true'
|| sec.dataset.showBorder === '1';
var adminPw = '';
var isAdmin = false;
var tableData = {
header: [],
rows: []
};
var dtInstance = null;
var loading = document.getElementById('vt-loading');
var viewDiv = document.getElementById('vt-view');
var adminDiv = document.getElementById('vt-admin');
function api(payload, cb, fail) {
fetch(ep + '?gid=' + encodeURIComponent(gid), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(function(response){
return response.json();
})
.then(cb)
.catch(fail || function(){});
}
function esc(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// Erlaubt nur ausgewählte sichere HTML-Tags in Tabellenzellen
function safeHtml(value) {
var escaped = esc(String(value || ''));
escaped = escaped.replace(
/<a href="([^&]+)"([^&]*)>(.*?)<\/a>/gi,
function(match, href, attrs, text) {
if (/^(https?:|mailto:|\/)/i.test(href.trim())) {
return '<a href="' + href + '" target="_blank" rel="noopener">'
+ text
+ '</a>';
}
return match;
}
);
escaped = escaped.replace(
/<(\/?(b|i|strong|em|br))>/gi,
'<$1>'
);
return escaped;
}
function toast(message, isError) {
var toastBox = document.getElementById('vt-toast');
toastBox.textContent = message;
toastBox.style.display = 'block';
toastBox.style.background = isError ? '#e74c3c' : '#1d9e75';
setTimeout(function(){
toastBox.style.display = 'none';
}, 3000);
}
function loadDT(callback) {
if (
window.jQuery
&& window.jQuery.fn
&& window.jQuery.fn.dataTable
) {
window.$ = window.jQuery;
callback();
return;
}
var jqueryScript = document.createElement('script');
jqueryScript.src = 'https://code.jquery.com/jquery-3.7.1.min.js';
jqueryScript.onload = function() {
var dataTablesScript = document.createElement('script');
dataTablesScript.src =
'https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js';
dataTablesScript.onload = function() {
var dataTablesCss = document.createElement('link');
dataTablesCss.rel = 'stylesheet';
dataTablesCss.href =
'https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css';
document.head.appendChild(dataTablesCss);
window.$ = window.jQuery;
callback();
};
document.head.appendChild(dataTablesScript);
};
document.head.appendChild(jqueryScript);
}
// Tabelle anzeigen
function renderView() {
if (dtInstance) {
try {
dtInstance.destroy();
} catch (error) {}
dtInstance = null;
}
var tableClass = 'table';
if (doStripes) tableClass += ' table-striped';
if (doHover) tableClass += ' table-hover';
if (doBorder) tableClass += ' table-bordered';
var html =
'<table id="vt-dt" class="' + tableClass
+ '" style="width:100%;">'
+ '<thead><tr>';
tableData.header.forEach(function(headerValue) {
html += '<th>' + esc(headerValue) + '</th>';
});
html += '</tr></thead><tbody>';
tableData.rows.forEach(function(row) {
html += '<tr>';
row.forEach(function(cellValue) {
html += '<td>'
+ safeHtml(cellValue).replace(/\n/g, '<br>')
+ '</td>';
});
html += '</tr>';
});
html += '</tbody></table>';
viewDiv.innerHTML = html;
viewDiv.style.display = 'block';
if (adminDiv) adminDiv.style.display = 'none';
if (loading) loading.style.display = 'none';
loadDT(function() {
dtInstance = window.$('#vt-dt').DataTable({
scrollX: true,
pageLength: pgLen,
searching: doSearch,
ordering: doSort,
paging: true,
info: true,
dom: 'lfrtip',
language: {
search: 'Suche:',
lengthMenu: '_MENU_ Einträge',
info: '_START_–_END_ von _TOTAL_',
zeroRecords: 'Keine Einträge',
paginate: {
first: '«',
last: '»',
next: '›',
previous: '‹'
}
},
initComplete: function() {
if (!doBorder) removeBorders();
},
drawCallback: function() {
if (!doBorder) removeBorders();
}
});
});
}
function removeBorders() {
var table = document.getElementById('vt-dt');
if (!table) return;
table.querySelectorAll('th, td').forEach(function(element) {
element.style.setProperty('border', 'none', 'important');
element.style.setProperty('border-bottom', 'none', 'important');
});
var wrapper = document.querySelector('.dataTables_wrapper');
if (wrapper) {
wrapper.querySelectorAll('th, td').forEach(function(element) {
element.style.setProperty('border', 'none', 'important');
});
}
}
// Admin-Editor
function renderAdmin() {
if (viewDiv) viewDiv.style.display = 'none';
if (!adminDiv) return;
adminDiv.style.display = 'block';
if (loading) loading.style.display = 'none';
var html =
'<div style="display:flex;justify-content:space-between;'
+ 'align-items:center;flex-wrap:wrap;gap:0.5rem;'
+ 'padding:0.75rem 1rem;margin-bottom:1rem;'
+ 'border-radius:8px;background:rgba(0,0,0,0.05);'
+ 'border:1px solid #ddd;">'
+ '<strong>✏️ Editor – ' + esc(gid) + '</strong>'
+ '<div style="display:flex;gap:0.4rem;flex-wrap:wrap;">'
+ '<button onclick="vtAddRow()" class="vt-btn-sm">'
+ '+ Zeile'
+ '</button>'
+ '<button onclick="vtAddCol()" class="vt-btn-sm">'
+ '+ Spalte'
+ '</button>'
+ '<button onclick="vtExport()" class="vt-btn-sm">'
+ '⬇ CSV'
+ '</button>'
+ '<label class="vt-btn-sm" style="cursor:pointer;">'
+ '⬆ CSV'
+ '<input type="file" id="vt-csv-file" accept=".csv" '
+ 'style="display:none;" onchange="vtImportCSV(this)">'
+ '</label>'
+ '<button onclick="vtSave()" '
+ 'class="vt-btn-primary vt-btn-sm">'
+ '💾 Speichern'
+ '</button>'
+ '<button onclick="vtLogout()" '
+ 'class="vt-btn-ghost vt-btn-sm">'
+ 'Logout'
+ '</button>'
+ '</div>'
+ '</div>';
/*
* KORREKTUR:
* Hier beginnt die Kopfzeile direkt mit den tatsächlichen
* Spaltenüberschriften. Die bisherige zusätzliche erste,
* leere TH-Zelle wurde entfernt.
*/
html +=
'<div style="overflow-x:auto;">'
+ '<table id="vt-etbl" '
+ 'style="border-collapse:collapse;white-space:nowrap;">'
+ '<thead><tr>';
tableData.header.forEach(function(headerValue, columnIndex) {
html +=
'<th draggable="true" '
+ 'data-col="' + columnIndex + '" '
+ 'style="padding:6px;border-bottom:2px solid #ddd;'
+ 'cursor:grab;user-select:none;min-width:120px;" '
+ 'title="Spalte ziehen zum Umsortieren">'
+ '<span style="color:#aaa;font-size:0.8rem;'
+ 'margin-right:4px;">⠿</span>'
+ '<input type="text" '
+ 'class="vt-hinp" '
+ 'data-col="' + columnIndex + '" '
+ 'value="' + esc(headerValue) + '" '
+ 'style="background:transparent;border:none;'
+ 'font-weight:700;width:75%;outline:none;cursor:text;">'
+ '<span onclick="vtDelCol(' + columnIndex + ')" '
+ 'style="color:#aaa;cursor:pointer;font-size:0.7rem;'
+ 'margin-left:4px;">✕</span>'
+ '</th>';
});
/*
* Diese letzte leere Kopfzelle ist korrekt.
* Sie gehört zur Löschspalte am Ende jeder Datenzeile.
*/
html +=
'<th style="width:30px;"></th>'
+ '</tr></thead>'
+ '<tbody id="vt-ebody">';
tableData.rows.forEach(function(row, rowIndex) {
html += '<tr data-row="' + rowIndex + '">';
row.forEach(function(cellValue, columnIndex) {
html +=
'<td style="padding:3px;border-bottom:1px solid #ddd;'
+ 'min-width:160px;vertical-align:top;">'
+ '<textarea '
+ 'data-row="' + rowIndex + '" '
+ 'data-col="' + columnIndex + '" '
+ 'class="vt-cel" '
+ 'style="width:100%;min-width:150px;'
+ 'box-sizing:border-box;background:transparent;'
+ 'border:1px solid #eee;border-radius:4px;'
+ 'padding:4px;resize:vertical;overflow-y:auto;'
+ 'min-height:34px;font-size:0.85rem;">'
+ esc(cellValue)
+ '</textarea>'
+ '</td>';
});
html +=
'<td>'
+ '<span onclick="this.closest(\'tr\').remove()" '
+ 'style="color:#aaa;cursor:pointer;font-size:1.1rem;">'
+ '×'
+ '</span>'
+ '</td>'
+ '</tr>';
});
html +=
'</tbody>'
+ '</table>'
+ '</div>';
adminDiv.innerHTML = html;
adminDiv.querySelectorAll('textarea.vt-cel').forEach(function(textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
textarea.oninput = function() {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
};
});
// Drag & Drop der Zeilenreihenfolge
var dragSourceRow = null;
adminDiv.querySelectorAll('#vt-ebody tr').forEach(function(rowElement) {
rowElement.addEventListener('dragstart', function(event) {
dragSourceRow = rowElement;
event.dataTransfer.effectAllowed = 'move';
setTimeout(function() {
rowElement.style.opacity = '0.4';
}, 0);
});
rowElement.addEventListener('dragend', function() {
rowElement.style.opacity = '1';
adminDiv.querySelectorAll('#vt-ebody tr').forEach(function(row) {
row.style.outline = '';
});
});
rowElement.addEventListener('dragover', function(event) {
event.preventDefault();
rowElement.style.outline = '2px solid #1d9e75';
});
rowElement.addEventListener('dragleave', function() {
rowElement.style.outline = '';
});
rowElement.addEventListener('drop', function(event) {
event.preventDefault();
event.stopPropagation();
rowElement.style.outline = '';
if (dragSourceRow && dragSourceRow !== rowElement) {
var tbody = document.getElementById('vt-ebody');
var rows = Array.from(
tbody.querySelectorAll('tr')
);
var sourceIndex = rows.indexOf(dragSourceRow);
var targetIndex = rows.indexOf(rowElement);
if (sourceIndex < targetIndex) {
tbody.insertBefore(
dragSourceRow,
rowElement.nextSibling
);
} else {
tbody.insertBefore(
dragSourceRow,
rowElement
);
}
}
});
});
// Drag & Drop der Spalten
var dragSourceColumn = null;
adminDiv
.querySelectorAll('#vt-etbl thead th[draggable]')
.forEach(function(headerCell) {
headerCell.addEventListener('dragstart', function(event) {
dragSourceColumn = parseInt(
headerCell.dataset.col,
10
);
event.dataTransfer.effectAllowed = 'move';
setTimeout(function() {
headerCell.style.opacity = '0.4';
}, 0);
});
headerCell.addEventListener('dragend', function() {
headerCell.style.opacity = '1';
adminDiv
.querySelectorAll('#vt-etbl thead th')
.forEach(function(cell) {
cell.style.outline = '';
});
});
headerCell.addEventListener('dragover', function(event) {
event.preventDefault();
headerCell.style.outline = '2px solid #1d9e75';
});
headerCell.addEventListener('dragleave', function() {
headerCell.style.outline = '';
});
headerCell.addEventListener('drop', function(event) {
event.preventDefault();
event.stopPropagation();
headerCell.style.outline = '';
var targetColumn = parseInt(
headerCell.dataset.col,
10
);
if (
dragSourceColumn === null
|| dragSourceColumn === targetColumn
) {
return;
}
tableData = getEditorData();
var movedHeader = tableData.header.splice(
dragSourceColumn,
1
)[0];
tableData.header.splice(
targetColumn,
0,
movedHeader
);
tableData.rows = tableData.rows.map(function(row) {
var movedCell = row.splice(
dragSourceColumn,
1
)[0];
row.splice(
targetColumn,
0,
movedCell
);
return row;
});
renderAdmin();
});
});
}
function getEditorData() {
var data = {
header: [],
rows: []
};
document
.querySelectorAll('#vt-etbl .vt-hinp')
.forEach(function(input) {
data.header.push(input.value);
});
var rowMap = {};
document
.querySelectorAll('#vt-ebody textarea.vt-cel')
.forEach(function(textarea) {
var rowIndex = textarea.dataset.row;
var columnIndex = parseInt(
textarea.dataset.col,
10
);
if (!rowMap[rowIndex]) {
rowMap[rowIndex] = [];
}
rowMap[rowIndex][columnIndex] = textarea.value;
});
Object.keys(rowMap)
.sort()
.forEach(function(rowKey) {
data.rows.push(rowMap[rowKey]);
});
return data;
}
window.vtAddRow = function() {
var columnCount = tableData.header.length;
var rowIndex = Date.now();
var row = document.createElement('tr');
row.dataset.row = rowIndex;
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++) {
var cell = document.createElement('td');
cell.style.cssText =
'padding:3px;'
+ 'border-bottom:1px solid #ddd;'
+ 'min-width:120px;';
var textarea = document.createElement('textarea');
textarea.dataset.row = rowIndex;
textarea.dataset.col = columnIndex;
textarea.className = 'vt-cel';
textarea.style.cssText =
'width:100%;'
+ 'box-sizing:border-box;'
+ 'background:transparent;'
+ 'border:1px solid transparent;'
+ 'border-radius:4px;'
+ 'padding:4px;'
+ 'resize:vertical;'
+ 'overflow:hidden;'
+ 'min-height:32px;'
+ 'font-size:0.85rem;';
textarea.oninput = function() {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
};
cell.appendChild(textarea);
row.appendChild(cell);
}
var deleteCell = document.createElement('td');
deleteCell.innerHTML =
'<span '
+ 'style="color:#aaa;cursor:pointer;font-size:1.1rem;" '
+ 'onclick="this.closest(\'tr\').remove()">'
+ '×'
+ '</span>';
row.appendChild(deleteCell);
document
.getElementById('vt-ebody')
.appendChild(row);
};
window.vtDelCol = function(columnIndex) {
if (!confirm('Spalte löschen?')) return;
tableData = getEditorData();
tableData.header.splice(columnIndex, 1);
tableData.rows = tableData.rows.map(function(row) {
row.splice(columnIndex, 1);
return row;
});
renderAdmin();
};
window.vtAddCol = function() {
var columnName = prompt('Spaltenname:');
if (!columnName) return;
tableData = getEditorData();
tableData.header.push(columnName);
tableData.rows = tableData.rows.map(function(row) {
return row.concat(['']);
});
renderAdmin();
};
window.vtSave = function() {
tableData = getEditorData();
api(
{
action: 'save',
password: adminPw,
data: tableData
},
function(response) {
if (response.success) {
toast(response.message || 'Gespeichert!');
renderView();
} else {
toast(response.error || 'Fehler', true);
}
}
);
};
window.vtLogout = function() {
api(
{
action: 'logout'
},
function() {
isAdmin = false;
adminPw = '';
renderView();
}
);
};
window.vtImportCSV = function(input) {
var file = input.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(event) {
var lines = event.target.result
.split(/\r?\n/)
.filter(function(line) {
return line.trim();
});
if (!lines.length) return;
// Semikolon oder Komma erkennen
var separator =
lines[0].indexOf(';') !== -1
? ';'
: ',';
function parseLine(line) {
// UTF-8-BOM entfernen
line = line.replace(/^\uFEFF/, '');
var result = [];
var currentValue = '';
var insideQuotes = false;
for (var index = 0; index < line.length; index++) {
var character = line[index];
if (character === '"') {
insideQuotes = !insideQuotes;
} else if (
character === separator
&& !insideQuotes
) {
result.push(currentValue.trim());
currentValue = '';
} else {
currentValue += character;
}
}
result.push(currentValue.trim());
// Leere letzte Spalten entfernen
while (
result.length > 0
&& result[result.length - 1] === ''
) {
result.pop();
}
return result;
}
var header = parseLine(lines[0]);
var rows = lines
.slice(1)
.map(parseLine)
.filter(function(row) {
return row.some(function(cell) {
return cell !== '';
});
});
// Alle Datenzeilen auf die Anzahl der Kopfspalten bringen
var columnCount = header.length;
rows = rows.map(function(row) {
while (row.length < columnCount) {
row.push('');
}
return row.slice(0, columnCount);
});
tableData.header = header;
tableData.rows = rows;
renderAdmin();
toast(
'CSV importiert – bitte prüfen und speichern!'
);
};
reader.readAsText(file, 'UTF-8');
input.value = '';
};
window.vtExport = function() {
window.open(
ep
+ '?action=csv&gid='
+ encodeURIComponent(gid),
'_blank'
);
};
// Login durch Strg + Doppelklick
document.addEventListener('dblclick', function(event) {
if (event.ctrlKey) {
event.preventDefault();
vtOpenLogin();
}
});
// Handy-Admin-Trigger
var mobileTrigger =
sec.dataset.mobiletrigger
|| 'shake';
var isTouch =
'ontouchstart' in window
|| navigator.maxTouchPoints > 0;
if (mobileTrigger === 'button' && isTouch) {
var adminButton = document.createElement('button');
adminButton.innerHTML = '⚙️';
adminButton.style.cssText =
'position:fixed;'
+ 'bottom:1.5rem;'
+ 'left:1rem;'
+ 'width:44px;'
+ 'height:44px;'
+ 'border-radius:50%;'
+ 'background:#1d9e75;'
+ 'border:none;'
+ 'color:#fff;'
+ 'font-size:1.2rem;'
+ 'cursor:pointer;'
+ 'z-index:9998;'
+ 'box-shadow:0 2px 8px #00000066;'
+ 'display:flex;'
+ 'align-items:center;'
+ 'justify-content:center;';
adminButton.onclick = function() {
vtOpenLogin();
};
document.body.appendChild(adminButton);
} else if (mobileTrigger === 'tap4') {
var tapCount = 0;
var tapTimer = null;
sec.addEventListener('touchend', function() {
tapCount++;
clearTimeout(tapTimer);
tapTimer = setTimeout(function() {
tapCount = 0;
}, 1500);
if (tapCount >= 4) {
tapCount = 0;
vtOpenLogin();
}
});
} else if (mobileTrigger === 'shake') {
var previousX = 0;
var previousY = 0;
var previousZ = 0;
var lastShakeTime = 0;
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', function(event) {
var acceleration = event.accelerationIncludingGravity;
if (!acceleration) return;
var difference =
Math.abs((acceleration.x || 0) - previousX)
+ Math.abs((acceleration.y || 0) - previousY)
+ Math.abs((acceleration.z || 0) - previousZ);
previousX = acceleration.x || 0;
previousY = acceleration.y || 0;
previousZ = acceleration.z || 0;
if (difference > 25) {
var now = Date.now();
if (now - lastShakeTime > 1500) {
lastShakeTime = now;
vtOpenLogin();
}
}
});
}
}
// Strg + Umschalt + A
document.addEventListener('keydown', function(event) {
if (
event.ctrlKey
&& event.shiftKey
&& (
event.key === 'A'
|| event.key === 'a'
)
) {
event.preventDefault();
vtOpenLogin();
}
});
window.vtOpenLogin = function() {
api(
{
action: 'check'
},
function(response) {
if (response.loggedIn) {
isAdmin = true;
renderAdmin();
return;
}
var modal =
document.getElementById('vt-modal');
var title =
document.getElementById('vt-modal-title');
var setupWrapper =
document.getElementById('vt-setup-wrap');
var loginWrapper =
document.getElementById('vt-login-wrap');
var loginButton =
document.getElementById('vt-login-btn');
var errorBox =
document.getElementById('vt-err');
if (errorBox) {
errorBox.style.display = 'none';
}
if (response.firstRun) {
if (title) {
title.textContent = '⚙️ Erst-Einrichtung';
}
if (setupWrapper) {
setupWrapper.style.display = 'block';
}
if (loginWrapper) {
loginWrapper.style.display = 'none';
}
if (loginButton) {
loginButton.textContent = 'Einrichten';
}
} else {
if (title) {
title.textContent = '🔐 Admin-Login';
}
if (setupWrapper) {
setupWrapper.style.display = 'none';
}
if (loginWrapper) {
loginWrapper.style.display = 'block';
}
if (loginButton) {
loginButton.textContent = 'Anmelden';
}
setTimeout(function() {
var passwordInput =
document.getElementById('vt-pw');
if (passwordInput) {
passwordInput.focus();
}
}, 100);
}
if (modal) {
modal.style.display = 'flex';
}
}
);
};
document.getElementById('vt-login-btn').onclick = function() {
var errorBox =
document.getElementById('vt-err');
var isSetup =
document.getElementById('vt-setup-wrap')
.style.display !== 'none';
var password = isSetup
? (
document.getElementById('vt-setup-pw')
|| {value: ''}
).value
: (
document.getElementById('vt-pw')
|| {value: ''}
).value;
var passwordConfirmation = isSetup
? (
document.getElementById('vt-setup-pw2')
|| {value: ''}
).value
: '';
var payload = isSetup
? {
action: 'setup',
password: password,
password2: passwordConfirmation
}
: {
action: 'login',
password: password
};
api(payload, function(response) {
if (response.success) {
adminPw = password;
isAdmin = true;
document.getElementById('vt-modal')
.style.display = 'none';
renderAdmin();
} else {
if (errorBox) {
errorBox.textContent =
response.error
|| 'Falsches Passwort.';
errorBox.style.display = 'block';
}
}
});
};
var passwordInput =
document.getElementById('vt-pw');
if (passwordInput) {
passwordInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
document
.getElementById('vt-login-btn')
.click();
}
});
}
// Start
api(
{
action: 'check'
},
function(checkResponse) {
if (checkResponse.loggedIn) {
isAdmin = true;
}
api(
{
action: 'load'
},
function(loadResponse) {
if (
loadResponse.success
&& loadResponse.data
) {
tableData = loadResponse.data;
}
renderView();
},
function() {
renderView();
}
);
},
function() {
renderView();
}
);
})();
</script>
</section>
CSS:
Code: Alles auswählen
padding-top: ~"@{paddingTop}rem" !important;
padding-bottom: ~"@{paddingBottom}rem" !important;
background-color: @bgColor !important;
.vt-card {
background: @cardColor !important;
border-radius: 20px;
box-shadow: 20px 30px 60px -10px rgba(0, 0, 0, 0.35);
padding: 20px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
width: 100% !important;
max-width: ~"@{tableWidth}%" !important;
margin-left: auto !important;
margin-right: auto !important;
}
/* Sichtbare Kopfzeile der veröffentlichten DataTables-Tabelle */
.vt-card .dataTables_scrollHead table.dataTable thead th {
background-color: @headBg !important;
color: @headText !important;
font-size: ~"@{headFontSize}px" !important;
font-weight: 600;
padding: 8px 30px 8px 12px !important;
white-space: nowrap;
border: none !important;
position: relative;
}
/* Kopfzeile der normalen Tabelle ohne DataTables-Scroll */
.vt-card #vt-view > table.dataTable thead th {
background-color: @headBg !important;
color: @headText !important;
font-size: ~"@{headFontSize}px" !important;
font-weight: 600;
padding: 8px 30px 8px 12px !important;
white-space: nowrap;
border: none !important;
position: relative;
}
/* Kopfzeile der Vorschau innerhalb von Mobirise */
.vt-card #vt-dummy table thead th {
background-color: @headBg !important;
color: @headText !important;
font-size: ~"@{headFontSize}px" !important;
font-weight: 600;
padding: 8px 12px !important;
white-space: nowrap;
border: none !important;
}
/* Kopfzeile im Admin-Editor */
.vt-card #vt-etbl thead th {
background-color: @headBg !important;
color: @headText !important;
font-size: ~"@{headFontSize}px" !important;
font-weight: 600;
white-space: nowrap;
border: none !important;
}
/* Platz und Position der Sortierpfeile */
.vt-card .dataTables_scrollHead table.dataTable thead th.sorting::before,
.vt-card .dataTables_scrollHead table.dataTable thead th.sorting::after,
.vt-card .dataTables_scrollHead table.dataTable thead th.sorting_asc::before,
.vt-card .dataTables_scrollHead table.dataTable thead th.sorting_asc::after,
.vt-card .dataTables_scrollHead table.dataTable thead th.sorting_desc::before,
.vt-card .dataTables_scrollHead table.dataTable thead th.sorting_desc::after,
.vt-card #vt-view > table.dataTable thead th.sorting::before,
.vt-card #vt-view > table.dataTable thead th.sorting::after,
.vt-card #vt-view > table.dataTable thead th.sorting_asc::before,
.vt-card #vt-view > table.dataTable thead th.sorting_asc::after,
.vt-card #vt-view > table.dataTable thead th.sorting_desc::before,
.vt-card #vt-view > table.dataTable thead th.sorting_desc::after {
right: 8px !important;
}
/* Unsichtbare Kopfzeilen-Kopie im DataTables-Scrollkörper ausblenden */
.vt-card .dataTables_scrollBody table.dataTable thead {
height: 0 !important;
}
.vt-card .dataTables_scrollBody table.dataTable thead tr {
height: 0 !important;
}
.vt-card .dataTables_scrollBody table.dataTable thead th {
height: 0 !important;
max-height: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
border: 0 !important;
line-height: 0 !important;
font-size: 0 !important;
background: transparent !important;
color: transparent !important;
}
.vt-card .dataTables_scrollBody table.dataTable thead th::before,
.vt-card .dataTables_scrollBody table.dataTable thead th::after {
display: none !important;
}
/* Tabellenzellen */
.vt-card table.dataTable tbody tr td,
.vt-card table tbody tr td {
background-color: @cardColor !important;
color: @textColor !important;
font-size: ~"@{bodyFontSize}px" !important;
padding: 8px 12px;
border-bottom: 1px solid @borderColor !important;
vertical-align: middle;
}
/* Zeilenstreifen */
&[data-show-stripes="true"] .vt-card table.dataTable tbody tr:nth-child(even) td,
&[data-show-stripes="true"] .vt-card table tbody tr:nth-child(even) td,
&[data-show-stripes="1"] .vt-card table.dataTable tbody tr:nth-child(even) td,
&[data-show-stripes="1"] .vt-card table tbody tr:nth-child(even) td {
background-color: @stripeColor !important;
}
/* Hover-Effekt */
&[data-show-hover="true"] .vt-card table.dataTable tbody tr:hover td,
&[data-show-hover="true"] .vt-card table tbody tr:hover td,
&[data-show-hover="1"] .vt-card table.dataTable tbody tr:hover td,
&[data-show-hover="1"] .vt-card table tbody tr:hover td {
background-color: @accentColor !important;
color: #ffffff !important;
}
/* Ladeanzeige */
.vt-loading {
color: #888;
text-align: center;
padding: 2rem;
font-size: 0.9rem;
}
/* Admin-Modal */
.vt-modal {
position: fixed;
inset: 0;
background: #000000cc;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
}
.vt-modal-box {
background: #fff;
border: 1px solid #ddd;
border-radius: 14px;
padding: 2rem;
width: 100%;
max-width: 380px;
margin: 1rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.vt-modal-hd {
display: flex;
justify-content: space-between;
align-items: center;
}
.vt-modal-hd h3 {
font-size: 1rem;
margin: 0;
font-weight: 700;
}
/* Eingabefelder */
.vt-input {
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 8px;
padding: 0.65rem 1rem;
font-size: 0.9rem;
width: 100%;
box-sizing: border-box;
outline: none;
font-family: inherit;
}
/* Fehlermeldung */
.vt-err {
background: #fef2f2;
border: 1px solid #fca5a5;
border-radius: 6px;
padding: 0.5rem;
color: #dc2626;
font-size: 0.82rem;
}
/* Primärer Button */
.vt-btn-primary {
flex: 1;
background: @accentColor;
color: #fff;
border: none;
border-radius: 8px;
padding: 0.65rem;
font-weight: 700;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1.2;
box-sizing: border-box;
}
/* Sekundärer Button */
.vt-btn-ghost {
flex: 1;
background: transparent;
border: 1px solid #ddd;
color: #888;
border-radius: 8px;
padding: 0.65rem;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1.2;
box-sizing: border-box;
}
/* Kleine Admin-Buttons einschließlich CSV-Upload-Label */
.vt-btn-sm {
background: #f0f0f0;
color: #333;
border: 1px solid #ddd;
border-radius: 5px;
padding: 4px 10px;
min-height: 29px;
font-size: 0.78rem;
font-family: inherit;
font-weight: 400;
line-height: 1.2;
cursor: pointer;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
white-space: nowrap;
margin: 0;
}
/* Verstecktes Dateifeld im CSV-Upload-Label */
.vt-btn-sm input[type="file"] {
display: none !important;
}
/* Horizontaler Tabellen-Scroll */
#vt-view,
#vt-view .dataTables_wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
#vt-view table {
min-width: 400px;
}
/* Abstand oberhalb der Tabelle */
.dataTables_wrapper table {
margin-top: 15px !important;
}
/* DataTables-Bedienelemente */
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
margin-bottom: 5px;
color: #00276d !important;
}
/* Suchfeld */
.dataTables_wrapper .dataTables_filter input {
border: 1px solid #fff !important;
margin-right: 12px;
}
/* Fokus-Schatten des Suchfelds */
.dataTables_wrapper .dataTables_filter input:focus {
outline: none !important;
box-shadow: 6px 6px 8px rgba(50, 50, 50, 0.3) !important;
}
/* Auswahlfeld für Anzahl der Einträge */
.dataTables_wrapper .dataTables_length select {
border: 1px solid #fff !important;
}
/* Smartphone */
@media (max-width: 576px) {
.vt-card {
max-width: 100% !important;
}
.dataTables_filter input {
max-width: 170px;
margin-top: 10px;
}
}
Ich habe diese
VN-Tabelle getestet und geprüft und halte sie nun für einwandfrei. Eine nützliche Erweiterung, zumal Mobirise 5 eine solche Tabelle nicht bietet. Die Tabelle aus Mobirise 4 ist zwar verwendbar, funktioniert in Mobirise 5 jedoch nur eingeschränkt.
Vor allem haben wir eine
online editierbare Tabelle, die als
CMS fungiert.