TinyMCE als Livewire Component
Laravel Livewire ist eine massive Arbeitserleichterung für dynamische Webapps. Aber bei der Kombination mit externen JavaScript-Bibliotheken wie TinyMCE gibt es eine Falle: Livewire und der Editor kämpfen um die Kontrolle über das DOM.
Das Problem: Livewire zerstört den Editor
Livewire ersetzt bei jedem Server-Roundtrip die DOM-Elemente seiner Komponente. Wenn TinyMCE an ein <textarea> innerhalb einer Livewire-Komponente gebunden ist, wird die TinyMCE-Instanz bei jedem Update zerstört und müsste neu initialisiert werden. Der Editor verliert den Fokus, der Cursor springt, Undo-History geht verloren – in der Praxis unbenutzbar.
Das gleiche Problem betrifft übrigens alle JavaScript-Bibliotheken, die eigene DOM-Elemente erzeugen: Quill, CKEditor, CodeMirror, Select2, Flatpickr. Die Lösung ist identisch.
Die Lösung: wire:ignore + Events
Zwei Mechanismen lösen das Problem:
wire:ignore– Verhindert, dass Livewire den umschlossenen DOM-Bereich bei Updates anfasst. TinyMCE behält die Kontrolle über sein<textarea>.@this.set()– Über denchange-Event von TinyMCE synchronisieren wir den Editor-Inhalt manuell zurück in die Livewire-Property.
Implementation
<!-- Blade-Template -->
<div>
<div wire:ignore>
<textarea id="editor" wire:model="document">{{ $document }}</textarea>
</div>
<button type="submit" wire:click="save">Speichern</button>
@script
<script>
tinymce.init({
selector: '#editor',
license_key: 'gpl',
setup: function (editor) {
editor.on('change input', function () {
$wire.set('document', editor.getContent());
});
}
});
</script>
@endscript
</div>Language:html
Was hier passiert
wire:ignoreauf dem Container-Div schützt die Textarea vor Livewire-Morphing.@scriptist Livewire 3 Syntax – das Script wird automatisch evaluiert, auch wenn die Komponente via Turbolinks/navigate geladen wird. In Livewire 2 müsste man stattdessenwindow.addEventListener('livewire:load', ...)verwenden.$wire.set()(Livewire 3) ersetzt@this.set()(Livewire 2). Beide setzen eine PHP-Property direkt vom JavaScript aus.license_key: 'gpl'– Seit TinyMCE 7 (2024) ist die Community-Edition unter GPL lizenziert. Ohne diesen Key zeigt TinyMCE eine Warnmeldung. Für kommerzielle Projekte, die nicht GPL-kompatibel sind, brauchen Sie einen kostenpflichtigen API-Key.
Häufige Stolperfallen
Mehrere Editoren auf einer Seite: Der Selector '#editor' funktioniert nur für ein Element. Bei mehreren TinyMCE-Instanzen (z.B. in einer Livewire-Schleife) brauchen Sie einen dynamischen Selector wie '.tinymce-editor' und müssen jede Instanz einzeln mit tinymce.get() ansprechen.
Initiales Laden: Wenn $document serverseitig befüllt ist, muss der Inhalt als Value der Textarea gerendert werden ({{ $document }}), nicht per wire:model allein – denn Livewire hydratisiert erst nach dem initialen Render.
Filament-Alternative: Wer Filament nutzt, braucht TinyMCE in vielen Fällen nicht. Filaments eingebauter Rich-Text-Editor (basierend auf Tiptap) ist Livewire-nativ und hat das DOM-Problem nicht. TinyMCE lohnt sich, wenn Sie spezifische Features brauchen (Templates, Mail-Merge, erweiterte Tabellen), die Tiptap nicht bietet.
Das allgemeine Pattern
Die Kombination wire:ignore + manueller Event-Sync funktioniert für jede JavaScript-Bibliothek, die eigene DOM-Elemente erzeugt. Das Pattern ist immer gleich:
Container mit
wire:ignoreumschließenJS-Bibliothek im
@script-Block initialisierenÄnderungen per
$wire.set()zurück an Livewire synchronisierenFalls nötig:
$wire.on()für serverseitige Updates, die den Editor-Inhalt ändern sollen
Dokumentation: Livewire wire:ignore | TinyMCE Laravel Integration