Ein Lösungsansatz

Integration von JavaScript: Wie man das Chaos vermeidet

Sven Kölpin

© Shutterstock.com / alphaspirit

Die wartbare Integration von JavaScript in serverseitig getriebene Webanwendungen stellt eine große Herausforderung dar. Dazu existiert bisher kein standardisierter Weg. Bislang greifen viele auf Inline-Scripting zurück, was oft zu Nachteilen führt. Dieser Artikel zeigt, wie JavaScript und JSF sauber zusammengeführt werden können.

Die Verwendung von JavaScript ist bei der Entwicklung von Webanwendungen nicht mehr wegzudenken. Um die Wartbarkeit einer Webapplikation auch noch bei einem hohen JavaScript-Anteil zu garantieren, ist eine strikte Codeorganisation im Frontend von großer Wichtigkeit. Die Erfahrung zeigt jedoch, dass dies bei JSF nur schwierig zu erreichen ist. Der Grund dafür liegt im deklarativen und serverseitigen Ansatz des Frameworks, bei dem die Kapselung clientseitiger Logik in XML-Tags einer direkten Verwendung von JavaScript vorgezogen wird (vgl. Client Behaviors).

Es existiert demzufolge bisher kein standardisierter Weg für die unmittelbare Integration von eigenem JavaScript-Code in JSF-Komponenten. Ganz im Gegenteil: Schaut man in die aktuelle JSF-Spezifikation, so scheint diese die Möglichkeit der Interaktion zwischen JSF und JavaScript, mit Ausnahme der für die AJAX-Funktionalität benötigten eigenen JavaScript-Bibliothek, vollständig zu ignorieren. Deshalb wird bei der Verbindung von JavaScript mit JSF in den meisten Fällen einfach Inline-Scripting, also das direkte Einbetten von JavaScript-Code im HTML, verwendet. Eine häufige Folge dieses Ansatzes ist schwierig zu wartender „Spaghetticode“, der an die früheren JSP-Zeiten erinnert, mit dem Unterschied, dass nun JavaScript anstatt Java mit HTML vermischt wird.

Separation of structure and behavior

Unter dem Oberbegriff „unobtrusive JavaScript“ werden seit einigen Jahren Paradigmen für den Einsatz von JavaScript in Webanwendungen zusammengefasst. Eines der Hauptprinzipien dieser Sammlung betrifft die strikte Trennung von JavaScript und HTML („Separation of structure and behavior“).

Das Trennen von Markup und Scripting hat viele Vorteile. Aus dem Blickwinkel der Arbeitsorganisation betrachtet, bietet sich der Nutzen einer klaren Aufgabentrennung. Webdesigner erstellen das Markup und Design, während Webentwickler die nötige GUI-Logik in eigenen JavaScript-Dateien implementieren können.

Der größte Vorteil der Trennung ergibt sich aber in erster Linie aus der erzwungenen Codeorganisation: Inline-Scripting macht Projekte auf Dauer unwartbar, weil es keine eindeutigen Anlaufpunkte für clientseitige Logik und Module gibt. Vor allem mit wachsender Codebasis führt Inline-Scripting häufig zu einer Verletzung vieler Grundprinzipien der modernen Softwareentwicklung (z. B. Wiederverwendbarkeit (DRY) oder Separation of Concerns). Des Weiteren erweist sich die Fehlersuche mit den im Browser vorhandenen Tools bei einer Vermischung von JavaScript mit HTML oft nicht als trivial.

Die Auslagerung der Skriptlogik in externe Dateien hingegen zwingt zur Modularisierung und führt somit implizit zu einer besseren Organisation und zu einer höheren Wiederverwendbarkeit des clientseitigen Codes.

Status quo und Herausforderungen

Leider birgt die größte Stärke von JavaScript, nämlich die Dynamik der Sprache, bei fehlender Erfahrung große Gefahren – es gibt eine enorme Vielzahl an Wegen, die zum gleichen Ziel führen (Listing 1). Aufgrund dessen ist man in einem Enterprise-Projekt förmlich dazu gezwungen, feste Grundstrukturen für die Entwicklung von JavaScript-Komponenten vorzugeben, um so einheitlichen und damit wartbaren JavaScript-Code zu garantieren. Das kann entweder explizit über vorher definierte Entwicklungsrichtlinien oder implizit mithilfe von clientseitigen Bibliotheken erreicht werden.

var SomeNameSpace = SomeNameSpace || {}; 
//constructor function
SomeNameSpace.DatePicker = function () { 
  this.init = function () { 
  }; 
}; 
//module pattern
SomeNameSpace.DatePicker = (function () { 
  var init = function () { 
  }; 
  return { 
      init: init 
  }; 
})(); 
//js-object
SomeNameSpace.DatePicker = { 
  init: function () { 
  } 
}; 
//es6 class
class DatePicker { 
  init() { 
  }
};

Eine zweite Herausforderung liegt darin, Daten aus JSF in JavaScript verfügbar zu machen. Bei der Erstellung wiederverwendbarer JSF-Komponenten ist beispielsweise die Übergabe des jeweiligen ID-Präfixes einer Komponente an ein JavaScript-Modul essenziell. Die Kenntnis der Client-ID ist nämlich unabdingbar für die Manipulation des in einer JSF-Komponente enthaltenen HTML-Codes durch JavaScript (Listing 2, Zeile 5 ff.). Weil JSF und JavaScript aber heterogene Laufzeitumgebungen haben (Server vs. Client) und externe JavaScript-Dateien ohne Weiteres vom serverseitigen Rendering-Prozess ausgeschlossen sind, muss die Datenübergabe bereits beim Rendern der JSF-Seite auf dem Server geschehen. Listing 3 zeigt einen gängigen Ansatz hierfür.

Neben der Übergabe der Client-ID sind außerdem die Informationen wichtig, die von außen deklarativ über Composite-Attribute an eine JSF-Komponente gereicht werden. Die meisten dieser Parameter haben häufig auch Relevanz für die in JavaScript implementierte clientseitige Logik. Als Beispiel sei hier das Format eines Datumsfelds genannt, das dynamisch an eine JSF-Komponente übergeben und dann im JavaScript-Code verwendet werden soll (Listing 2, Zeile 7 und Listing 3, Zeile 14).

1  var SomeNameSpace = SomeNameSpace || {}; 
2
3  SomeNameSpace.DatePicker = (function () { 
4    var init = function (settings) { 
5        var dateInputField = document 
6                       .getElementById(settings.clientId + ":dateInput"); 
7        var dateFormat = settings.dateFormat; 
8       //create datepicker...
9    }; 
1    return { 
1        init: init 
1    }; 
1  })();
1  <?xml version="1.0" encoding="UTF-8"?> 
2  <!DOCTYPE html> 
3  <html xmlns="http://www.w3.org/1999/xhtml" 
4        xmlns:composite="http://xmlns.jcp.org/jsf/composite" 
5        xmlns:jsf="http://xmlns.jcp.org/jsf"> 
6  <composite:interface> 
7     <composite:attribute name="format" type="java.lang.String"/> 
8  </composite:interface> 
9  <composite:implementation> 
10     <input type="text" jsf:id="dateInput"/> 
11     <script> 
12         SomeNameSpace.DatePicker.init({ 
13             clientId: "#{cc.clientId}", 
14             dateFormat: "#{cc.attrs.format}" 
15         }); 
16     </script> 
17  </composite:implementation> 
18  </html>

Optimierungspotenzial

Die aktuellen Ansätze für die Integration von JavaScript in JSF-Komponenten haben den Nachteil, dass für die Initialisierung der jeweiligen JavaScript-Module noch immer Inline-Scripting verwendet werden muss (Listing 3). Für eine ideale Frontend-Architektur nach dem Prinzip der „Separation of structure and behavior“ sind solche Medienbrüche allerdings nicht optimal. Vielmehr sollten die Module, wie man es in JSF gewohnt ist, über XML-Tags konfiguriert und die eigentliche Implementierung der clientseitigen Logik ausschließlich in ausgelagerten JavaScript-Dateien vorgenommen werden. Zusätzlich müssen aus den eingangs erwähnten Gründen klare Grundstrukturen für die jeweiligen JavaScript-Module zur Verfügung stehen, um einen Wildwuchs an Patterns von vorneherein zu vermeiden.

Konkreter Lösungsansatz

Listing 4 und Listing 5 zeigen die Konfiguration und Implementierung der vorher gezeigten DatePicker-Komponente, ohne dass Inline-Scripting verwendet werden muss. Für die Umsetzung dieses Lösungsansatzes wurde die Bibliothek „nicole“ entwickelt. Listing 4 zeigt die Konfiguration einer Komponente mit nicole und Listing 5 ein JavaScript-Modul mit nicole und jQuery. Diese Bibliothek ermöglicht es, externe JavaScript-Module in JSF über XML-Tags zu konfigurieren und gleichzeitig eine einheitliche Grundstruktur für die clientseitige Script-Logik zu schaffen. nicoles Sourcecode und die Dokumentation sind öffentlich auf GitHub hinterlegt.

Die Erstellung von JavaScript-Komponenten mit nicole erfordert zwei Schritte. Zuerst muss ein nicole-Modul im HTML konfiguriert werden. Dazu ist die in Listing 4 gezeigte DatePicker-Komponente in Zeile 12 ff. um ein zusätzliches XML-Tag <nicole:module/> erweitert. Dieses signalisiert, dass zu der JSF-DatePicker-Komponente ein JavaScript-Modul mit dem Namen DatePicker gehört. In Zeile 13 wird zusätzlich das Datumsformat per <nicole:jsparameter/>-Tag an das nicole-Modul weitergereicht. In HTML befindet sich, im Gegensatz zu den vorher gezeigten Ansätzen, keine einzige Zeile JavaScript-Code.

Als Zweites muss zu einer in HTML deklarierten nicole-Komponente stets ein entsprechendes JavaScript-Modul mit dem gleichen Namen existieren (Listing 5). Es wird über Nicole.module(„modulName“, function(){}) initialisiert (Zeile 1). In der als zweiten Parameter übergebenen Funktion (Zeile 1–5) wird dann die eigentliche Clientlogik der Komponente implementiert.

1  <?xml version="1.0" encoding="UTF-8"?> 
2  <!DOCTYPE html> 
3  <html xmlns="http://www.w3.org/1999/xhtml" 
4        xmlns:jsf="http://xmlns.jcp.org/jsf" 
5        xmlns:nicole="http://openknowledge.de/nicole"
6        xmlns:composite="http://xmlns.jcp.org/jsf/composite">
7  <composite:interface> 
8      <composite:attribute name="format" type="java.lang.String"/>
9  </composite:interface> 
10 <composite:implementation> 
11     <input type="text" jsf:id="dateInput"/> 
12     <nicole:module modulename="DatePicker"> 
13         <nicole:jsparameter name="format" value="#{cc.attrs.format}"/>
14     </nicole:module> 
15  </composite:implementation> 
16  </html>
1  Nicole.module("DatePicker", function () { 
2      this.$elm("datePicker").datepicker({ 
3          dateFormat: this.parameter("format") 
4      }); 
5  });

nicole bietet außerdem Hilfsfunktionen, die die Entwicklung von JavaScript-Komponenten erheblich erleichtern. Beispielsweise ermöglichen folgende zwei Funktionen den Zugriff auf die DOM-Elemente einer Komponente:

  • this.elm(„id“): Über diese Methode bekommt man das zu der übergebenen ID gehörige DOM-Element aus der Komponente (z. B. this.elm(„datePicker“) liefert das Input-Feld datePicker aus der Komponente).
  • this.$elm(„id“): Wie this.elm, allerdings wird das jQuery-Element zu der ID geliefert. Diese Funktion erfordert, dass jQuery eingebunden wurde.

Auch für den Zugriff auf von außen an die Komponente übergebene Parameter existiert eine Hilfsfunktion: this.parameter(„parameter“). Mit dieser Funktion kann auf die in der JSF-Komponente übergebenen Parameter zugegriffen werden (z.B. this.parameter(„format“) liefert das in der JSF-Komponente deklarierte Datumsformat). Weitere Hilfsfunktionen können der Dokumentation auf GitHub entnommen werden.

Unter der Haube

Der in nicole implementierte Ansatz kommt ohne die Verwendung von Inline-Scripting aus. Relevante Parameter aus JSF werden während des Renderns auf dem Server unsichtbar in das zu übertragene HTML eingefügt und so an den Browser übermittelt.

Für die versteckte Datenübertragung werden Hidden-Input-Felder verwendet, die über die seit HTML5 standardisierten data-*Attribute um Informationen aus JSF erweitert werden (Listing 6). Alle zu einer jeweiligen JSF-Komponente wichtigen Daten sind so im Browser für die entsprechenden JavaScript-Module in einem einzigen DOM-Element verfügbar. Der große Vorteil dieses Prinzips: Es existiert keine einzige Zeile JavaScript im HTML-Code, und HTML wird auf legalem Weg als Vermittler zwischen JSF und JavaScript genutzt.

Die <nicole:module/>-Komponente übernimmt das Rendern des in Listing 6 gezeigten Hidden-Input-Felds. Die <nicole:jsparameter/>-Komponenten werden in die data-*-Attribute des Felds übersetzt. nicoles mitgeliefertes JavaScript-API findet, sobald der Browser den DOM-Baum fertig geladen hat, automatisch die entsprechenden Hidden-Input-Felder und initialisiert die dazugehören JavaScript-Module.

1  <html xmlns="http://www.w3.org/1999/xhtml"> 
2  <input id="contentForm:datePickerCC:datePicker" 
3         name="contentForm:datePickerCC:datePicker" type="text"/> 
4  <input id="contentForm:datePickerCC:nicoleCC:nicole" 
5         type="hidden" 
6         data-modulename="DatePicker" 
7         data-format="dd-mm-yy"
8         data-clientid="contentForm:datePickerCC"/> 
9  </html>

Fazit

Der gezeigte Ansatz bringt Vorteile für die Strukturierung von Code bei der Entwicklung von JavaScript-lastigen JSF-Anwendungen mit sich. Anders als bisher wird eine strikte Trennung von JSF und JavaScript-Code ermöglicht. Dadurch entsteht ein klares Pattern für die Entwicklung von JSF-Komponenten, und die Wartbarkeit von Projekten wird gesteigert. Zusätzlich werden weitere Nachteile, die durch die Verwendung von Inline-Scripts entstehen, beseitigt. Beispielsweise können, vor allem bei älteren Browsern, viele Inline-Script-Tags die Ladegeschwindigkeit einer Seite negativ beeinflussen.

Eine wartbare Integration von JavaScript in serverseitig getriebene Webframeworks ist auch außerhalb von JSF eine Herausforderung. Deshalb ist der hier gezeigte Lösungsansatz in seinen Grundsätzen auch in anderen Welten, zum Beispiel in dem mit Java EE 8 kommenden MVC-1.0-Framework, denkbar.

Aufmacherbild: System integration concept with union of puzzle via Shutterstock.com / Urheberrecht: alphaspirit

Verwandte Themen:

Geschrieben von
Sven Kölpin
Sven Kölpin
Sven Kölpin ist Enterprise Developer bei der open knowledge GmbH in Oldenburg. Sein Schwerpunkt liegt auf der Entwicklung webbasierter Enterprise-Lösungen mittels Java EE.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: