Javascript als Grundlage moderner Webtechnologien
Einleitung
Im Wandel der Zeit
Schon seit längerem ist zu beobachten, dass der Browser mehr und mehr zur wichtigsten Plattform zur Ausführung von Anwendungen avanciert. Nicht zuletzt hat HTML5 diese Entwicklung deutlich unterstrichen. Mit dem neuen Standard können Anwendungsentwickler auf eine Vielzahl von Funktionen zurückgreifen, die bisher nur klassischen Desktop-Applikationen vorbehalten waren. Auch SAP hat diesen Trend erkannt und mit SAPUI5 ein Framework auf den Markt gebracht, welches die Vorteile des neuen HTML-Standards in die SAP-Anwendungsentwicklung übertragen soll.
Neue Anforderungen an Entwickler
Wer mit SAPUI5 arbeiten will, muss gut mit Javascript umgehen können. Keine einfache Aufgabe für gestandene ABAP- und Java-Programmierer, denen die Eigenheiten von Javascript auf den ersten Blick sehr gewöhnungsbedürftig anmuten müssen. Zugegeben, anfangs erfordert es etwas Mut, um nicht zu sagen Überwindung, sich auf eine dynamisch typisierte, interpretierte Skriptsprache einzulassen. Wer den Sprung ins kalte Wasser jedoch überstanden hat, wird vielleicht mit keiner anderen Programmiersprache mehr arbeiten wollen. Denn Javascript bieten gerade in der Entwicklung der Präsentationslogik unglaublich viele Vorteile gegenüber ABAP und Java. Wie sich das genau ausdrückt, wollen wir uns im Folgenden etwas genauer ansehen. Ziel sollte es sein, eine eigene SAPUI5-App strukturell sauber und wartbar entwickeln zu können.
Von globalen Funktionen zu Closures
Schritt 1: Globale Funktionen und Variablen
In viele Webanwendungen wird ausschließlich mit global deklarierten Variablen und Funktionen gearbeitet. Folgendes Coding könnte entweder in einer eigenen .js -Datei oder in eine <script>-Bereich der HTML-Seite abgelegt sein.
var a = 1; function b(){ alert(a); } b();
Auf diese Weise ist sowohl Variable a als auch Funktion b von globaler Sichtbarkeit. Je umfangreicher das Coding wird, desto schwieriger wird es für den Anwendungsentwickler die Übersicht zu behalten. In objektorientierten Sprachen existiert zu Lösung dieser Problems die Klasse, welches logisches und konzeptionell zusammengehöriges Coding kapselt. ABAP und Java bieten genau dieses Feature an, weshalb man hier von klassenbasierter Objektorientierung spricht. In Javascript gibt es keine Klasse, welche sozusagen als Schema für Objekterzeugung verwendet werden könnte. Stattdessen vertraut man hier auf die prototypenbasierte Objektorientierung, sodass Objekte aus anderen Objekten (den sog. Prototypen) erzeugt werden. Um genau das zu tun, gibt es unterschiedliche Vorgehensweisen.
Schritt 2: Objekte als Namespaces
Wie wir gleich sehen werden, überführen wir das Coding aus Schritt 1 in einen etwas übersichtlicher Ansatz.
var myScope = myScope || {}; myScope.a = 1; myScope.b = function(){ alert(myScope.a); } myScope.b();
Zunächst erzeugen wir ein Javascript-Objekt. Falls das Objekt jedoch schon existieren sollte, weisen wir die bestehende Instanz zu. Falls "myScope" initial sein sollte, wird durch die geschweifte Klammern ein neues Objekt erzeugt. Danach erfolgt die Deklaration einer Eigenschaft und einer Methode auf Basis des Objektes. Methode b ist somit nicht mehr global sichtbar, sondern nur über das Objekt "myScope" erreichbar.
Es wird ersichtlich, dass Objekte als einfaches Mittel zur Strukturierung des Codings eingesetzt werden können. Auf diese Weise können nun logisch zusammengehörige Funktionen in gleiche Namespaces abgelegt werden. Allerdings erlaubt uns dieser Ansatz noch keine klassische Objekterzeugung im eigentlichen Sinne (sprich: aus Schema wird konkrete Instanz).
Schritt 3: Funktionskonstruktoren
Hier sehen wir nun die Definition eines Funktionskonstruktors, über welchen wir mit dem Schlüsselwortnewneue Instanzen erzeugen können.
function MyObject(a, b, c){ this.a= a; this.b= b; this.c= c; this.toString = function(){ return this.a+" "+this.b+" "+this.c; }; } var myObject = new MyObject(1,2,3); myObject.toString();
Rein theoretisch könnte man für alle fachlichen Entitäten (z.B.: Person, Rechnung etc.) solche Konstrukte erzeugen und die Logik als Funktionen analog zur obigen toString-Funktion implementieren. Wer auf eine etwas kompaktere, JSON-lastigere Syntax steht, kann auch folgenden Ansatz wählen.
function MyObject(a, b, c){ return { a: a, b: b, c: c, toString : function(){ return this.a+" "+this.b+" "+this.c; } }; } var myObject = new MyObject(1,2,3); myObject.toString();
Das Ergebnis der Objekterzeugung ist identisch zum vorigen Listing.
Schritt 4: Nutzung von prototype
Die akademisch korrekte Erweiterung von Javascript-Objekten zur Laufzeit wird über die prototype-Anweisung abgebildet. Ausgangssituation ist wiederum ein leeres Objekt, welches wir dynamisch über weitere Funktionen erweitern wollen.
var MyObject = function(){}; MyObject.prototype.constructor = MyObject; MyObject.prototype.a = 1; MyObject.prototype.b = function(){ alert(this.a); } // Objekterzeugung var myObject = new MyObject(); myObject.b(); // Objekt-Erweiterung zur Laufzeit MyObject.prototype.c = 5; alert(myObject.c);
Über die formale Definition eines Konstruktor und die Erzeugung eines leeren Objektes über die function Anweisung, können wir nun beliebig Gebrauch von dynamischen Erweiterung mittels prototype Gebrauch machen. Eine Erzeugung eines Objektes über {} hätte an dieser Stelle nicht ausgereicht. Im Folgende das gleiche Coding, nur etwas kompakter
var MyObject = function(){}; // JSON-Syntax MyObject.prototype = { constructor : MyObject, a : 1, b : function(){ alert(this.a); } } // Objekterzeugung, wie oben var myObject = new MyObject(); myObject.b(); MyObject.prototype.c = 5; alert(myObject.c);
Schritt 5: Closures
Nun sind wir zwar in der Lage, eine dynamsiche Objekterzeugung vorzunehmen, allerdings fehlt uns zur besseren Strukturierung des Codings die Möglichkeit, öffentliche und private Schnittstellen zu definieren. Wir benötigen ein Konzept zur Eingrenzung von Sichtbarkeitsbereichen, ohne auf Schlüsselworte wie public und private zurückgreifen zu müssen (die es in Javascript ohnehin nicht gibt^^). Um dies zu erreichen, implementieren wir ein Closure bzw. eine sich selbst ausführende, anonyme Funktion.
(function(){ // Neuer Sichtbarkeitsbereich }());
Zugegeben, auf den ersten Blick sieht dieses Konstrukt etwas seltsam aus. Aber im Prinzip ist es nichts anders als eine anonyme Funktion, welche nach ihrer Definition durch () sofort aufgerufen wird. Der gesamte Block wird dann nochmal von zwei Klammern umfasst und endet mit einem Semikolon. Im nächsten Listing wollen wir die anonyme Funktion mit ein paar Parametern versorgen.
(function(MYAPI, $, undefined){ }(window.MYAPI = window.MYAPI || {}, jQuery));
Der erste Parameter ist window.MYAPI = window.MYAPI || {}. Wir übergeben hiermit einen Namespace bzw. erzeugen in mit {}, falls er noch nicht existieren sollte. Falls wir jQuery verwenden (ist bei SAPUI5 ja der Fall) übergeben wir als zweiten Parameter das jQuery Objekt. Das hat den Hintergrund, dass wir im eigentlichen Closure das $ nicht neu definieren wollen. Gleiches gilt für das undefined-Schlüsselwort. Im Internet hat sich dieser best-practise zum Schutz vor Redifinition in den letzten Jahren durchgesetzt. Nun wollen wir den neuen Sichtbarkeitsbereich für unsere Zwecke nutzen.
(function(MYAPI, $, undefined){ // "privates" Attribut, nur innerhalb des Closures sichtbar var test = "It works!"; // "private" Funktion function createPopup(){ alert(test); } // Definition eines neuen Namespaces MYAPI.PERSON= function(){}; // Öffentliche Methode über den Namespace MYAPI.NEWSCOPE MYAPI.PERSON.prototype.doSomething = function(){ createPopup(); }; }(window.MYAPI = window.MYAPI || {}, jQuery)); // Außerhalb des Closures kann nur über den Namespace ein Aufruf stattfinden var person = new MYAPI.PERSON(); person.doSomething();
Mittels dieses Programmierparadigmas lassen sich zusätzliche Sichtbarkeitsbereiche schaffen. Außerdem kann das Coding modular aufgebaut werden. Für jedes Modul bzw. für jede Komponente wird ein Closure definiert und in einer eigenen .js-Datei abgelegt. In einer index.html führen wir dann alle .js-Dateien zusammen. Eine weitere typische Eigenschaft des Closures besteht darin, dass alle "privaten" Variablen selbst nach Verlassen der anonymen Funktion nicht initialisiert werden (im obigen Beispiel würde Variable "test" seinen Wert beim Verlassen der Funktion nicht verlieren). Mit anderen Worten werden die Werte nicht aus dem Speicher gelöscht, sondern bleiben für die Dauer der Programmausführung erhalten.
Idealerweise kombiniert man Closures und Funktionskonstruktoren, um in den Genuss maximaler Kapselung zu kommen. Unter folgendem Link kann obiges Listing bei js-Fiddle getestet werden.
Fazit
Mit Hilfe dieser Ansätze lassen sich auch komplexe Anforderungen übersichtlich in Javascript realisieren. Egal ob man nun mit SAPUI5 oder einem anderen Framework arbeitet, die hier gezeigten Techniken lassen sich immer beim Arbeiten mit Javascript anwenden.