Alpine.js: Das Schweizer Taschenmesser für dynamische Weboberflächen

Seite 3: Dynamisches HinzufĂĽgen von DOM-Elementen

Inhaltsverzeichnis

Das dynamische Hinzufügen von DOM-Elementen ist eine häufige Aufgabe in der Webentwicklung – hier dargestellt am Beispiel von Tabellen-Zeilen.

Das Hinzufügen von DOM-Elementen mit jQuery galt als ausgesprochen mühsam. In Vorträgen hat Caleb Porzio dazu stets Anekdoten und War Stories auf Lager, die er zum Verdeutlichen seiner Probleme mit diesem Framework anführt:

<div x-data="{ rounds: [] }">
    <button @click="rounds.push([
        Math.max(...rounds.map(r => r[0]), 0) + 1,
        (new Date()).toLocaleTimeString()
        ])">Runde hinzuf�gen</button>

    <table>
        <thead>
            <tr>
                <th>Runde</th>
                <th>Uhrzeit</th>
            </tr>
        </thead>

        <tbody>
            <template x-for="round in rounds">
                <tr>
                    <td x-text="round[0]"></td>
                    <td x-text="round[1]"></td>
                </tr>
            </template>
        </tbody>
    </table>
</div>

Die jQuery-Implementation packt den HTML-Code fĂĽr eine neue Tabellenzeile in einen String:

<div>
    <button id="add">Runde hinzuf�gen</button>

    <table id="roundtable">
        <thead>
            <tr>
                <th>Runde</th>
                <th>Uhrzeit</th>
            </tr>
        </thead>

        <tbody></tbody>
    </table>
</div>


<script>
    const rounds = [];

    $('#add').click(() => {
        const newRound = [
            Math.max(...rounds.map(r => r[0]), 0) + 1,
            (new Date()).toLocaleTimeString()
        ];
        rounds.push(newRound);

        $('#roundtable tbody').append(
            `<tr><td>${newRound[0]}</td>` +
            `<td>${newRound[1]}</td>` +
            '</tr>'
        );
    })
</script>

Auch wenn es andere Möglichkeiten gibt, das Hinzufügen von DOM-Elementen mit jQuery zu implementieren, demonstriert es anschaulich Porzios persönliche Erfahrung. Im Alpine-Code steht die zu erstellende Zeile direkt in der entsprechenden Tabelle. Die Schleife erinnert mit der x-for-Syntax ebenfalls an Vue.js – mit der Einschränkung, dass Schleifen bei Alpine nur an template-Tags erlaubt sind. Die Variable rounds ist bei Alpine auch lokal für den div-Container beschränkt. Bei jQuery gilt sie je nach Implementierung im Zweifelsfall sogar global, sodass auftretende Namenskonflikte zu Mehrarbeit führen.

Das Alpine.js-Framework unterstützt in seinen Attributen vollwertigen JavaScript-Code, inklusive aktueller Sprachkonstrukte wie Getter und Setter. Die rudimentäre Implementierung eines Eingabefelds für formatierte IBAN-Nummern führt das im folgenden Listing vor. Über x-model wird eine Variable sowohl lesend als auch schreibend an ein input-Feld gebunden – das heißt, alle Änderungen der Variable zeigen sich im Textfeld, und alle Änderungen im Textfeld werden in der Variable gespeichert.

<div x-data="{
        _realIban: '',
        get iban() {
            return this._realIban.replace(/(.{4})/g, '$1 ');
        },
        set iban(val) {
            this._realIban = val.toUpperCase().replace(/ /g,'');
        }
    }">

    <label for="iban">IBAN eingeben:</label>
    <input id="iban" type="text" x-model="iban" placeholder="DE07 1234 1234 1234 1234 12">
</div>

Ein Vergleich mit jQuery wäre an dieser Stelle überflüssig, da dessen Implementierung nicht wesentlich abweicht. Der Einsatz von Gettern und Settern erinnert an die computed-Properties von Vue.js, insbesondere durch deren Reaktivität.

Dropdown-Menüs sind häufig verwendete Komponenten, die für eine optimale User Experience JavaScript benötigen.

Dropdown-Komponenten lassen sich zwar mit reinem CSS implementieren, allerdings wird der Quellcode dabei schnell unübersichtlich und basiert dann auf eher unschönen Hacks. Aus diesem Grund greifen Entwickler tendenziell zu JavaScript-basierten Dropdowns, wie man sie auch mithilfe von Alpine.js oder jQuery implementieren kann:

<div x-data="{ open: false }">
    <button @click="open = ! open">Dropdown anzeigen</button>
    <div x-show="open" @click.outside="open = false" class="dropdown-content">
        Hallo Welt đź‘‹
    </div>
</div>

Hier zeigt sich erneut der imperative Charakter von jQuery gegenĂĽber Alpines datenbasierter, reaktiver Architektur:

<div>
    <button id="dropdown-toggle">Dropdown anzeigen</button>
    <div id="dropdown-content">
        Hallo Welt đź‘‹
    </div>
</div>

<p>test</p>

<script>
    $("#dropdown-content").hide()

    $("#dropdown-toggle").click(() => {
        $("#dropdown-content").toggle()
    })
</script>

Alpine bietet mit dem @click.outside-Attribut einen Mehrwert, denn eine solche Implementierung in jQuery wäre aufwendig und würde schnell zu ungewollten Seiteneffekten führen. Derartige Komfortfunktionen unterstreichen Caleb Porzios Motivation, Entwicklern das Leben zu vereinfachen. Häufig benötigte Operationen wie das Abfangen von Klicks außerhalb eines Elements sind im Framework enthalten und leicht umzusetzen.

Mit Alpine.js lassen sich Ressourcen auch asynchron laden und anzeigen.

Alpine bietet die Möglichkeit, kleinere eigenständige Komponenten zu entwickeln. Durch die Verlagerung sämtlichen Quellcodes direkt in den DOM statt in separaten script-Tags steigt die Übersichtlichkeit, und die Komponenten ähneln in ihrer Struktur Vue.js. Mit x-init wird JavaScript-Code in der Initialisierungs-Phase ausgeführt und kann so beispielsweise Daten initial laden. Asynchronität wird ebenfalls unterstützt:

<div x-data="{ fact: undefined }"
     x-init="fact = (await (await fetch('https://catfact.ninja/fact')).json()).fact">

    <p x-show="fact == undefined">Wird geladen...</p>
    <template x-if="fact != undefined">
        <div>
            <h1>Zuf�lliger Katzen-Fakt</h1>
            <p x-text="fact"></p>
        </div>
    </template>
</div>

In der jQuery-Implementierung im folgenden Listing lieĂźe sich der HTTP-Aufruf zwar auch mit fetch umsetzen, viele jQuery-Entwickler greifen aber eher zu den Funktionen des Frameworks.

<div>
    <p id="loading">Wird geladen...</p>
    <div id="content">
        <h1>Zuf�lliger Katzen-Fakt</h1>
        <p id="cat-fact"></p>
    </div>
</div>

<script>
    $('#content').hide()

    $.getJSON('https://catfact.ninja/fact', (data) => {
        $("#cat-fact").text(data.fact)
        $('#loading').hide()
        $('#content').show()
    });
</script>

In manchen Szenarien ist eine Referenz auf ein DOM-Element nötig. Hier hat jQuery einen Heimvorteil: Die einfache Selektion ist ein Kernfeature des Frameworks. Allerdings müssen an dieser Stelle wieder IDs oder Klassen genutzt werden, um das Element per JavaScript zu adressieren:

<div x-data>
    <button @click="$refs.text.remove()">Text entfernen</button>

    <p x-ref="text">Hallo Welt</p>
</div>

Alpine.js kann hingegen mit x-ref DOM-Elemente direkt als JavaScript-Variable speichern (unter $refs):

<div>
    <button id="remove">Text entfernen</button>

    <p id="text">Hallo Welt</p>
</div>

<script>
    $("#remove").click(() => {
        $("#text").remove();
    })
</script>

Wichtig ist aber, dass sich diese Direktive in einer Alpine-Komponente befindet: Das Elternelement muss also beispielsweise das x-data-Attribut haben, damit das Framework es verarbeitet. Das Gegenteil ist auch möglich, durch x-ignore ignoriert das Framework einzelne Elemente.

Analog zu den so genannten Watchers in Vue.js bietet Alpine x-effect. Mit diese Funktion wird JavaScript-Code ausgeführt, wenn sich das Innere der Komponente verändert, also, sobald ein Ausdruck neu evaluiert wird – das nächste Code-Beispiel führt das vor. Dabei wird nicht eine einzelne Variable isoliert betrachtet, sondern der gesamte Abhängigkeitsbaum eines DOM-Elements.

<div x-data="{ count: 0 }" x-effect="console.log('Z�hler ' + count)">
    <button @click="count++">Z�hler erh�hen</button>
</div>