PHP Tabellen Block

Anwendungen für Webseiten. Künstliche Intelligenz verwenden.
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

PHP Tabellen Block

Ungelesener Beitrag von Volker »

Hallo zusammen,

ich habe nun auch meine PHP Tabelle in einen Mobirise Block gepackt.
Nun wird also alles automatisch beim Veröffentlichen angelegt. SQlite DB usw. wird auf dem Server angelegt.
Passwort wie immer strg + Doppelklick. Passort: volker04

Ist schon im Download zum testen Tommy ;)

DEMO:https://niederastroth.de/0phptabelle/

Drag & Drop ist noch nicht eingebaut.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Moin,

sehe ich das richtig, das ist doch genau die Tabelle von Dir, die ich auch hier schon einmal ausprobiert hatte:

https://www.mobirise-tutorials.com/Tuto ... hp#MyTable

Nur hat das Script nun jede Menge JavaScript im HTML - wahrscheinlich um auch wieder schon den Style auch auf der Arbeitsseite zu sehen.

Soll ich das dennoch mal einbauen?

Wenn es jetzt nur darum geht, dass das PHP bereits mit integriert ist, glaube ich eigentlich nicht, dass ich das testen müsste. Das war ja damals schon OK.
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

Re: PHP Tabellen Block

Ungelesener Beitrag von Volker »

Das ist die php tabelle, aber jetzt als mbrext.Also kein upload per ftp usw
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Ja, nee - klar. Na ich baue das Teil dann auch mal ein ...

Ist zwar gleich aber eben ganz anders :D
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Wie lese ich denn meine CSV-Datei dort ein? Da verschieben sich die Spalten …

https://www.mobirise-tutorials.com/VN-B ... belle.html

Wenn ich eine ganz neue Überschrift und Tabelle anlege, verrutscht das ebenso wie beim Importieren meiner CSV-Datei. Das hat vielleicht etwas mit Deiner ersten Spalte zu tun, in der jetzt "Beispiel" steht (als Spalte, die einen Buchstaben breit ist). Diese Spalte fehlt dann beim Neuanlegen und alles verrutscht um eine Spalte.



Hier meine CSV:
Meine-CSV-Tabelle.zip
(714 Bytes) 2-mal heruntergeladen


... warte auf Anleitung.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Dann müsste da zwischen der eigentlichen Tabelle und den Feldern darüber (Einträge / Suchenfeld) ein Abstand von 10-20px rein, damit das nicht so zusammenklebt.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Da befindet sich eine leere Spalte in der SQLite-Datenbank, die ich nicht angelegt habe, deswegen verschieben sich die Spalten nach rechts:


SQLite Datenbank.jpg


verirrte, leere Spalte ganz links:


verirrte Spalte in Tabelle.jpg



In Deiner Beispiel-Tabelle ist diese Spalte nicht vorhanden.
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

Re: PHP Tabellen Block

Ungelesener Beitrag von Volker »

Deswegen sollst du ja immer testen 😉
Muss ich fixen alles
stobi_de
Moderator
Moderator
Beiträge: 1494
Registriert: Di 11. Okt 2022, 06:30

Re: PHP Tabellen Block

Ungelesener Beitrag von stobi_de »

Nun fehlt dann noch die geniale Umsetzung für ein Handy
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

Re: PHP Tabellen Block

Ungelesener Beitrag von Volker »

Was meinst damit? Stapeln kann ich einbauen anstatt scrollen
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Moin Frank,

was meinst Du jetzt?

Am Handy ist die optimale Umsetzung, dass die Tabelle horizontal scrollt. Alles andere ist und muss Murks sein, da der Platz nicht ausreicht.

So wie die andere Tabelle hier:

https://www.mobirise-tutorials.com/Tuto ... hp#MyTable
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

Re: PHP Tabellen Block

Ungelesener Beitrag von Volker »

So Tommy,

alles gefixt ;)

Und wie Du siehst sind die Daten noch genau so da wie vorher ;) Passiert also nix mit der DB wenn ich den Block ändere oder die Seite ;)

https://niederastroth.de/0phptabelle/

Drag&Drop
Abstand Suche geändert
DB korrigiert


Ist nun im aktuellen Download
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Ja - das scheint jetzt super zu funktionieren - auch mit dem Import meiner CSV-Datei.

↗️ https://www.mobirise-tutorials.com/VN-B ... ml#Tabelle

Ich habe heute keine Zeit mehr zum Testen aber so viel schon jetzt:

> Wieso sitzen im Administratorbereich die Spalten der Spaltenüberschriften nicht über den entsprechenden Tabellenspalten, sondern versetzt?

Admin-Editor.jpg


> Wie bekomme ich diesen hässlichen, roten Schatten da am Suchen-Feld weg? Eigentlich fehlt hier auch die Auswahl der Text- und Borderfarbe, die ich selbst auf weiß geändert habe. Im Dropdown "Einträge" (links) sind diese, weil weiß, jetzt gar nicht zu sehen.


Roten Schatten weg um das Suchen-Feld beim Select.jpg
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

Re: PHP Tabellen Block

Ungelesener Beitrag von Volker »

Feinarbeit mach ich morgen ;)

Muss ich selber erst mal gucken wegen dem roten rahmen
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Dann muss die Tabelle natürlich am Handy eine andere Breite als maximal 70% erhalten - das sieht ganz schlimm aus :eek:

Breite am Handy hier nur 70 Prozent - muss auf 100%.jpg


Ich habe das bei mir schon rein geschrieben:

Code: Alles auswählen

@media (max-width: 576px) {
    .vt-card {
        max-width: 100% !important;
    }
}
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Volker,

ich sehe gerade, dass in deiner Tabelle im Administratorbereich die Spalten sauber ausgerichtet sind. Warum ist das nach dem Import der CSV-Datei bei mir nicht der Fall? Da rutscht die Header-Zeile um genau eine Spalte nach rechts:


Admin-Editor.jpg


Das wäre eigentlich das Einzige, was noch geklärt werden muss. Kannst Du das noch ausfindig machen oder lässt Du es so? Du hast doch meine CSV-Datei - an der ist ja nichts außergewöhnlich, lese die doch mal in Deine Tabelle ein. Die Adressen sind von der KI, die gibt es nicht wirklich. Kannst die also auch gerne immer verwenden. Ich will es nur wegen meines Tutorials dazu wissen.

> Ich fände es auch schöner, wenn der Button zum ↑ CSV Import nicht so komisch nach oben versetzt dargestellt würde.

> Das Drag & Drop in der Header-Zeile (Ziehen und Ablegen) der Spalten funktioniert aber auch noch nicht richtig. Ich habe das jetzt bei Dir etwas verschoben und bekomme die letzte Spalte nicht nach hinten. Ich kann an meinem Laptop die Beschriftung in Deinem schwarzem Header nicht lesen, alles nur schwarz.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

> Noch eine grundsätzliche Idee.

Ich fände es besser, wenn Du Deine Block‑Erweiterungen für den Import in Gruppen sortieren würdest. Sonst wird die Ansicht früher oder später endlos und sehr unübersichtlich. Beim Erstellen einer .mbrext‑Datei kannst Du diese doch einer Gruppe zuordnen — z. B. "VN‑Blöcke". Ich habe meine ebenfalls in der Gruppe "Tommys" organisiert.

https://www.mobirise-tutorials.com/Mobi ... erator.php
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

Ich habe die Tabellenbreite in der Handy-Ansicht von 70 % auf 100 % erhöht und die Breite des Suchfelds auf 170 px reduziert — siehe Screenshot:

Code: Alles auswählen

@media (max-width: 576px) {
  .vt-card {
    max-width: 100% !important;
  }
  .dataTables_filter input {
    max-width: 170px;
    margin-top: 10px;
  }
}

Tabelle mobile Ansicht.jpg


> Mir ist noch aufgefallen, dass die Sortierpfeile ↕ nach oben und unten teilweise nicht korrekt rechts neben den Tabellenüberschriften, sondern unter dem Text angezeigt werden. Im Screenshot sind z.B. die Pfeile am "#" zu dicht und am "Vorname" hinter dem "e".

Vielleicht richtet sich hier die Platzierung durch die KI nach der Breite der "tr" und nicht der "th" Tags.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9126
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: PHP Tabellen Block

Ungelesener Beitrag von Tommy Herrmann »

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.

Ansicht Administrator.jpg


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, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;');
        }

        // Erlaubt nur ausgewählte sichere HTML-Tags in Tabellenzellen
        function safeHtml(value) {
            var escaped = esc(String(value || ''));

            escaped = escaped.replace(
                /&lt;a href=&quot;([^&]+)&quot;([^&]*)&gt;(.*?)&lt;\/a&gt;/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(
                /&lt;(\/?(b|i|strong|em|br))&gt;/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.
Volker
Moderator
Moderator
Beiträge: 3111
Registriert: Sa 12. Dez 2020, 22:35

Re: PHP Tabellen Block

Ungelesener Beitrag von Volker »

Ja sag mal :D

Du bist ja schneller als der Schall ;)

Dann hau ich das so mal in die neue version rein.
Antworten

Wer ist online?

Mitglieder in diesem Forum: Bing [Bot], Volker und 6 Gäste