Frameworkless Frontend und trotzdem glücklich?

Mit einigen Jahren JavaScript und Reactjs Erfahrung durfte ich Ende letzten Jahres (November 2019) Teil eines neuen Teams und eines neuen Projektes werden. Das Projekt ist ein Traumprojekt jeden Entwicklers. Ein grüne Wiese Projekt mit “freier” Technologiewahl. “Frei” in Form von man darf sich die Zeit für eine Risikoanalyse nehmen und moderne Tools und Frameworks evaluieren.

ReactJS kenne ich bereits aus den letzten Jahren. Der Wille Neues zu lernen trieb mich zu vue und svelte. Darüber liest man auf Twitter und Tech Blogs nur Gutes. Und es soll die Zukunft sein. Auch ember sieht sehr interessant aus. Oder Angular weil TypeScript? Oder Lightning Web Components? Darauf bin ich 2019 bei der EnterJS aufmerksam geworden und es hat mir recht gut gefallen. RxJS zu verwenden wäre auch nett, weil, Reaktiv ist in aller Munde. Und dann Styled-Components. CSS im JavaScript direkt an meiner Komponente schreiben. Okay… ich übertreibe gerade ein klein wenig 😉

Anforderungen bewerten

Gehen wir nochmal einen Schritt zurück. Vor der Frage nach Technologien stellt sich die Frage der Anforderungen. Unsere Anforderungen an die Benutzeroberfläche waren

  • Erstellen, Lesen, Aktualisieren und Löschen (Create, Read, Update, Delete; kurz CRUD).
    An einigen Stellen kann man es sich vorstellen wie eine TODO Liste.
  • Der Browser wird vom Anwender morgens im Büro auf seinem 27″ Monitor geöffnet. Bei Feierabend vielleicht geschlossen.
  • Das Finden und Auswählen von Dingen.
  • Interaktive Elemente a la Auf- und Zuklappen von Informationen.

Anforderungen, wie es vielleicht die meisten Anwendungen im B2B Bereich haben. Sogenannte CRUD Anwendungen. Müssen wir hierfür das Risiko eingehen, ein neues modernes JavaScript Framework einzuführen? Ich denke nicht. Für mich persönlich wäre ReactJS zwar nichts neues, mein Team allerdings weiß das es von Facebook kommt. Das wars dann mit den Kenntnissen darüber. Bei erwähnten Anforderungen stellt sich die Frage nach einen Framework meiner Meinung nach auch gar nicht. HTML für den Inhalt und CSS fürs bunt machen reicht völlig aus. JavaScript hat es zum Start des Projektes überhaupt nicht benötigt. Die Webseiten waren alle schnell genug. Das Finden und Auswählen von Dingen war vielleicht nicht ganz so schnell wie mit einer Single Page Application, aber immer noch schnell genug.

Anforderungen ändern sich

Mit der Zeit kam jedoch eine neue Anforderung hinzu:

  • Als Anwender kann ich nach dem Hinzufügen eines Elements direkt das nächste Element hinzufügen ohne scrollen zu müssen, damit ich meine Arbeit ungestört und schnell abschließen kann.

Was ist passiert? Der Anwender befindet sich weiter unten auf der Webseite und hat eine Art Todo Liste. (die Todo Liste dient als Beispiel, in Wirklichkeit ist es etwas größer und komplexer. Das technische Konzept ist jedoch das gleiche.) Dieser Liste können Elemente hinzugefügt und entfernt werden. Und das Ganze soll nun ohne kompletten Seiten Reload funktionieren.

Aus bestimmten Gründen ist der Button zum Hinzufügen eines neuen Elementes ein form, das ein POST macht. Ohne das form submit abzufangen und mit JavaScript zu behandeln findet ein kompletter Seitenreload statt. Der Anwender befindet sich nach dem Auswählen des Buttons wieder oben auf der Seite und muss Ort und Stelle suchen, an welcher er vorher war. Das geht schöner, und zwar ohne ein komplexes JavaScript Framework. Manch Einer denkt jetzt vielleicht an die guten alten jQuery Zeiten. Was mir bei dieser Lösung jedoch nicht gefällt:

  • jQuery wird als Dependency benötigt 😉
  • Ich muss mich selbst darum kümmern, dass erst das HTML Element existiert um dann eine JavaScript Funktion aufzurufen, die alles initialisiert.

Letzter Punkt wird hauptsächlich interessant, wenn der Anwendung weitere HTML Elemente dynamisch hinzugefügt werden. Um darauf reagieren zu können muss wiederum Code geschrieben werden. Die Komplexität unserer Anwendung steigt damit unnötig an.

Apropos dynamisch. Neben dem Hinzufügen eines neuen “Todos” an Ort und Stelle sollte das natürlich auch für das Speichern des ausgefüllten Todos funktionieren. Eine generische Lösung wollte daher gefunden werden. Vom Prinzip wie bei alten jQuery Anwendungen. Wir fragen das Backend an, bekommen ein fertig gerenderten HTML Abschnitt und fügen diesen der Webseite hinzu. Wie wir das Backend anfragen ist bereits mit den HTML Formularen beantwortet. Wir müssen lediglich das form submit abfangen und einen XMLHttpRequest senden.

Das Abfangen des form submit könnte mit einem globalen document.addEventListener('submit', handleSubmitEvents) gelöst werden. Wir haben jedoch den Weg mit Custom Elements gewählt.

Custom Elements

Der Browser bietet mit Custom Elements eine elegante Lösung, die Benutzeroberfläche progressiv mit JavaScript zu verbessern. Gegenüber jQuery Komponenten muss sich nicht mehr darum gekümmert werden, ob das HTML Element schon da ist oder nicht. Es wird ein Custom Element im Browser registriert und dieser kümmert sich um das Instanziieren sobald es möglich ist. Egal ob zuerst das HTML Element existiert und dann das Custom Element, oder zuerst das Custom Element und dann das HTML.

Es gibt zwei Möglichkeiten Custom Elements zu registrieren und zu verwenden:

  • Autonome benutzerdefinierte Elemente
    sind isoliert von Standard HTML Elementen und komplett eigenständig
  • Benutzerdefinierte Standardelemente (built in elements)
    erweitern Standard HTML Elemente mit zusätzlichem gewünschten Verhalten

Da unsere Anwendung bereits ohne JavaScript funktioniert und wir sie nur progressiv verbessern wollen fällt die Wahl auf built in custom elements. Folgendes Codebeispiel zeigt das Formular zum Hinzufügen eines neuen “Todo” Elementes und ein div in dem das “Todo” Element angezeigt wird. Damit aus dem button ein Built-In Custom Element wird definieren wir das Attribut is="ajax-submit". Dieses Attribut verknüpft das HTML mit einer JavaScript Custom Element Komponente.

<form action="/heroes" method="post">
  <input type="text" name="hero">
  <input type="text" name="superpower">
  <button type="submit" is="ajax-submit" data-target="hero-container">
    add new item
  </button>
</form>

<div id="hero-container">
</div>
class AjaxSubmitButton extends HTMLButtonElement {

  connectedCallback() {
    this.addEventListener("click", async (event) => {
      event.preventDefault();
      let html = await ajaxFormSubmit();
      document.getElementById(this.dataset.target).innerHTML = html;
    });
  }
}

customElements.define("ajax-submit", AjaxSubmitButton, {
  extends: "button",
});

Der Vorteil des Custom-Elements gegenüber jQuery (oder sonstiger eigener Implementierung) ist, dass der Browser sich darum kümmert, unser JavaScript mit dem HTML zu verbinden und eine Instanz des AjaxSubmitButtons zu erstellen.

Kommen zur Laufzeit weitere submit Buttons mit diesem Attribut hinzu, werden sie vom Browser automatisch mit unserem gewünschten Verhalten erweitert. Ist das JavaScript aus irgendwelchen Gründen nicht verfügbar funktioniert weiterhin das gute alte HTML Formular mit komplettem Seitenreload. Wir verbessern unsere Anwendung mit jedem weiteren Technologie-Layer, der zur Verfügung steht, Progressive Enhancement genannt. HTML beschreibt den Inhalt, CSS macht es bunt, und zu guter Letzt verbessern wir die Benutzererfahrung mit JavaScript.

Das Backend wird mit diesem Ansatz auch nicht komplizierter oder gar komplexer. Im Controller müssen wir erkennen, dass es sich um einen Ajax Request handelt. In dem Fall wird ein HTML Fragment gerendert, andernfalls wird die komplette Seite gerendert:

@PostMapping(value = "/heroes")
public String addSuperhero(
      @RequestParam String hero,
      @RequestParam String superpower,
      @RequestHeader(name = "X-Requested-With", defaultValue = "") String requestedWith,
      Model model
  ) {

    model.addAttribute("hero", hero);
    model.addAttribute("superpower", superpower);

    if ("ajax".equals(requestedWith)) {
        return "fragments/hero-fragment :: hero-fragment";
    }

    return "full-page-including-the-hero-fragment";
}

Auf dem Weg zur Single Page Application

Mittlerweile haben wir die ajax-submit Komponente in unserer Anwendung an sehr vielen Stellen im Einsatz. Bisher hat sie sich als robust und einfach bewährt. Jeder im Team hat verstanden wie man es einsetzt und wie es funktioniert. Wir brauchen keine Runtime a la ReactJS, wir müssen Hooks nicht verstehen, wir benötigen kein komplexes Build Setup. Wir müssen nicht überlegen wie wir vue Komponenten integrieren und wir müssen uns keine Gedanken bzgl Client Side only Funktionalität machen.

Kurz gesagt: Wir bauen unser Frontend ohne modernes JavaScript Framework und sind (trotzdem) glücklich.

Zugegeben, die Progressive Enhancement Denkweise ist herausfordernd. Daran zu denken und zu überlegen, wie Anforderungen ohne JavaScript gelöst werden können, hat unsere Industrie vielleicht verlernt? In der Vergangenheit haben mich Kollegen, die überwiegend im Backend unterwegs sind gefragt, wie man denn heutzutage ein Frontend baut. Nehme man da Angular oder React? Über Anforderungen war man sich da aber noch nicht bewusst… Es war zumindest nicht die Einleitung zur Technologie Frage.

Herausforderungen die kommen (könnten)

  • Progressive Enhancement Denkweise
    Sobald unsere Anwendung einen gewissen Charakter moderner Single Page Applications erreicht hat, wird das natürlich der Default werden (im Geiste). Hier gilt es weiterhin an die Basis zu denken und nicht sofort an JavaScript only Lösungen.
  • History Handling
    Vor und Zurück nach bestimmten Aktionen ist bisher keine Anforderung. Sollte diese Anforderung kommen ist das aber auch kein Hexenwerk. Nach JavaScript Aktionen die URL mit der history API ändern ist im Bereich des Möglichen 😉
  • State
    Nach bestimmten Aktionen den State an mehreren Stellen im Browser aktualisieren. Hier muss denke ich abewägt werden, ob Bibiliotheken eine Daseinsberechtigung bekommen, oder ob Custom Events ausreichen.