/ Tech Insights

So eine Art "Rich-Text-Editor" im Browser mit contentEditable

Während der Entwicklung des Talents Connect Messenger-Tools habe ich ein paar Erfahrungen mit dem Thema "Rich-text editing" im Browser gemacht und hier zusammengetragen.

Werfen wir zunächst einen Blick auf ein Mockup der Anforderung.

Bildschirmfoto-2017-06-02-um-09.40.17

Der User – hier ein Recruiter der in Kontakt mit Bewerbern tritt – soll die Möglichkeit haben, generische Nachrichten zu verfassen, die Mithilfe von Variablen personalisiert werden können.

Level 1: Text mit Platzhaltern

Der erste Schritt:
Ein mehrzeiliges Eingabefeld mit stylebaren Platzhaltern muss her.

HTML bietet für mehrzeilige Eingabebereiche das <textarea> Element an. Da Textareas jedoch keine weiteren HTML-Elemente beinhalten können, kann damit kein angereicherter Text geschrieben werden.
Die Anforderung an die "Variablen-Tags" aus dem Entwurf, konnte hierüber also nicht erfüllt werden.

Der HTML-Standard hält für derartige Zwecke die contentEditable Eigenschaft parat.
Ein Element wie <div> wird dadurch änderbar wie ein Textfeld. Weiteres HTML ist innerhalb eines divs natürlich möglich:

<div contentEditable="true" class="fake-textarea">
  Hallo <span class="tag">firstName</span>
</div>

Somit können Elemente, wie hier das span.tag als Inline-Element innerhalb des Feldes gerendert werden und sind somit auch komplett per CSS stylebar.

.tag {
  padding: 0 5px;
  border: 1px solid black;
  display: inline-block;
}

display: inline-block verhindert hier etwaiges umbrechen innerhalb eines Tags.

Bonus Level - Semantik

Das span kann zum var Element umbenannt werden, um semantisch zu signalisieren, dass es sich hier um eine Platzhalter-Variable handelt.

Level 2: Einfügen von Tags

Der nächste Schritt:
Eine solche Platzhalter-Variable soll an die aktuelle Cursor-Position im Textfeld eingefügt werden.

Zunächst muss also die Cursor-Position abgefragt werden.
Mittels der Window Selection Web-API und deren Funktionen getSelection() und getRangeAt() kommt man an die aktuelle Position des Cursors.

Nun muss das HTML noch zu einem Fragment gemacht werden, dass dadurch dort einfügbar wird.
Range.createContextualFragment() erzeugt ein solches Fragment aus einem String, dass dann dort eingefügt werden kann.

Zusammengefügt ergibt sich folgende Funktion:

function insertHtml(html) {
  range = document.getSelection().getRangeAt(0);
  fragment = document.createRange().createContextualFragment(html);
  range.insertNode(fragment);
}

Damit kann nun also HTML in ein editierbares Feld eingefügt werden.

Zu beachten ist, dass man zur Sicherheit prüfen sollte, ob es sich beim aktuell fokussierten Element tatsächlich um das contenteditable Feld handelt, um zu vermeiden, dass irgendwo anders auf der Seite HTML einfügbar ist.

Bonus Level - Löschen von Tags

Um bei der Betätigung von Backspace nicht Teile eines Tags im Eingabefeld zu löschen und dadurch die HTML Struktur zu verfälschen, wird dieses .tag als uneditierbar markiert.
Denn das contentEditable-Attribut bietet auch die Möglichkeit dies explizit zu verneinen:

<div class="fake-textarea" contentEditable="true">
  Hallo <span class="tag" contentEditable="false">firstName</span>
</div>

Ein Tag wird bei Backspace "im Ganzen" entfernt werden, als handle es sich um ein einzelnes "Zeichen" im Text.

Boss Fight - Click vs. Mousedown

Die oben beschriebene Funktion zum Einfügen von HTML an die Cursor-Position funktioniert in allen modernen Browsern™.

Doch zurück zur oben gezeigten Anforderung.
Die auswählbaren Tags sollen in einem Flyout-Menü platziert werden und per Klick darin ausgewählt werden.

Die Schwierigkeit:
Es zeigte sich, dass bei Klick auf einen solchen Menüeintrag der Fokus auf eben diesen Eintrag wechselt, er bleibt also nicht mehr im Textfeld.
Die in Abschnitt 2 beschriebene Erkennung der Cursor-Position liefert somit etwas völlig Falsches.

Die Lösung für dieses Problem ist die Verwendung des onmousedown Events.
In diesem Moment ist der Fokus, ist anders als bei click, noch nicht aus dem Textfeld heraus und die korrekte Cursor-Position ist sichergestellt.

Und so sehen die Beispiele zusammengefasst in Aktion dann aus:

Sascha L

Sascha L

Sascha entwickelt seit Dekaden benutzerfreundliche und innovative Frontend Konzepte.

Mehr lesen