Ext JS Kochbuch - Kapitel 2: Modularer Code

Inhalt

1. JavaScript
2. Klassen und Namespaces
3. Vererbung
4. Best Practices für Ext JS Klassen
5. Mixins


1. JavaScript

Viele Programmiersprachen geben direkt in ihrer Syntax Bausteine wie Namespaces, Klassen oder Vererbung zur Strukturierung des Quelltextes fest vor. JavaScript macht hier kaum Vorgaben. Die Flexibilität der Sprache ermöglicht es allerdings, die bekannten Konstrukte durch erprobte Patterns (siehe z.B. "Learning JavaScript Design Patterns") oder durch bestehende Bibliotheken zu nachzubilden.

Diese Flexibilität ist Fluch und Segen von JavaScript zugleich. Es gibt zum einen den Ansatz, durch einen Präprozessor wie CoffeScript und TypeScript die Syntax von JavaScript zu erweitern. Eine Klasse in TypeScript würde z.B. wie folgt ausschauen:

class Hello {

    whatToSay: string = 'Hello World';

    saySomething() {
        alert(this.whatToSay);
    }
}

var hello = new Hello();
hello.saySomething();

Hello World in TypeScript

Wiederrum möglich ist es, eine Konvention einzuführen bei der eines der vielen bekannten Patterns verwendet wird, um den Code mit objektorientierten Prinzipien zu strukturieren. Folgender Quelltext demonstriert die Verwendung des Revealing Module Patterns:

var hello = (function() {

    var whatToSay = 'Hello World';

    var saySomething = function() {
        window.alert(whatToSay);
    }

    return {
        saySomething: saySomething
    }

})();

hello.saySomething();

Hello World mit purem JavaScript

Ebenso kann man bestehende Bibliotheken nutzen, welche weder die Syntax von JavaScript verändern noch die Einhaltung von Patterns per Konvention verlangen. Jene Bibliotheken stellen lediglich neue JavaScript-Funktionen zur Verfügung und geben die Art ihrer Verwendung fest vor. Ext JS, bzw. der "Ext JS Core" ist eine diese Bibliotheken.

2. Klassen und Namespaces

Die einfachste Möglichkeit, eine Klasse zu definieren bietet die globale Methode Ext.define. Hierbei gibt man sowohl den Namespace der Klasse als auch den Namen der Klasse als ersten Parameter (String) an. Es folgt die Definition der Klasse als JavaScript-Objekt.

Ext.define('MyNamespace.Hello', {

    whatToSay: 'Hello World',

    saySomething: function() {
         alert(this.whatToSay);
    }
});

var hello = Ext.create('MyNamespace.Hello'); 
hello.saySomething();

Hello World mit dem Ext JS Klassensystem
Beispiel: Class

Statt Ext.create könnte man auch folgende Schreibweise verwenden:

var hello = new MyNamespace.Hello();

Man muss sich bei der direkten Verwendung des Konstruktors bewusst sein, dass es zu einem JavaScript-"Reference Error" kommen kann, wenn die Klasse im Moment der Verwendung noch nicht durch den Browser geladen wurde. Ext.create ist hingegen in der Lage, im Fall der Fälle fehlende Abhängigkeiten mithilfe des Ext.Loader synchron nachzuladen. Das synchrone Nachladen von Abhängigkeiten sollte zwar generell vermieden werden, aber etwas Wartezeit während der Ausführung ist in der Regel dennoch vertretbarer als eine JavaScript-Exception.

Entsprechend der Naming Conventions von Ext JS sollten Klassen nur alpha-numerische Zeichen enthalten. Von Unterstriche und Zahlen wird abgeraten. Das Ext JS Framework setzt auf die "CamelCase"-Schreibweise.

3. Vererbung

In vielen Fällen wird man die Entwicklung nicht mit einer leeren Klasse beginnen. Es bietet sich eher an, von den mehr als 300 Klassen aus dem Ext JS Framework zu erben. So kann man auf der existierenden Funktionalität aufbauen und spezifisch den zu verändernden Aspekt zu überschreiben.

Im folgenden Beispiel erben wir vom bereits bekannten Ext.window.Window. Statt bei der Initialisierung das Property html zu setzen, integrieren wir uns dieses mal besser in die Ext JS Welt und geben Explizit an, welche Kind-Elemente das Window haben soll (mehr zu den Items später).

Ext.define('MyNamespace.HelloWindow', {
    extend: 'Ext.window.Window',
    require: [],

    title: 'Test',
    height: 200,
    width: 400,

    whatToSay: 'Hello World ',

    constructor: function () {

        this.items = [{
            xtype: 'text',
            text: this.whatToSay + new Date().getFullYear(),
        }];

        this.callParent(arguments);
    }
});

Ext.onReady(function () {

    var helloWindow = Ext.create('MyNamespace.HelloWindow');
    helloWindow.show();
});

Beispiel: Inheritence

Wichtig ist das Schlüsselwort extend, welches angibt, von welcher Klasse geerbt werden soll. Es gibt viele Möglichkeiten, eine erbende Klasse in Ext JS zu definieren, die Nachfolgenden Best Practices können Ihnen als Orientierung dienen.

4. Best Practices für Ext JS Klassen

Dies sind einige persönliche Best Practices von Johannes Hoppe:

  1. Für eigene Klassen sollte von Anfang an ein eigener Namespace verwendet werden. Der Pfad, wo Dateien für den Namespace zu finden sind, sollten beim Ext.Loader wie folgt registriert sein:
    Ext.Loader.setConfig({
        enabled: true,
        paths: { 'MyNamespace': 'my_own_path' }
    });
  2. Abhängigkeiten sollten stets mit require definiert werden. Dies versetzt Ext JS in die Lage, die fehlenden Dateien asynchron nachzuladen, sofern diese nicht bereits bekannt sind. Die fehlende Definition kann unter Umständen während der Entwicklung gar nicht aufwallen, wenn bereits eine vorherige Klasse diese geladen hat. Da ein solcher Fehler schnell geschehen ist, sollte require auch dann angebgeben werden, wenn der Wert nur ein leeres Array ist. Als Entwickler gibt man so ein Statement ab, dass man zum einen Kenntnis von der Existenz des Ext.Loader hat und dass man selbst eindeutig keine weiteren Abhängigkeiten verwendet.

  3. Der Konstruktor der Eltern-Klasse muss immer mit this.callParent(arguments); aufgerufen werden. Der Aufruf des Eltern-Konstruktors findet ansonsten nicht statt.
    Man könnte auch

    constructor: function (config) {
        this.callParent(config);
    }

    verwenden. Aber mit arguments kann man auch dann das Konfigurations-Objekt übergeben, wenn man selbst das Konfigurations-Objekt im eigenen Code nicht benötigt.

  4. Das setzen von Default-Werten geht sehr einfach durch die Schreibweise als Properties. Im vorigen Beispiel sind dies title, height und width. Das Setzen funktioniert problemlos für einfache Datentypen wie bool, int und string. Unter keinen Umständen sollte den Properties ein Objekt zugewiesen werden. (z.B. store: new Ext.data.Store()) Die erzeugte Objekt wird in diesem Fall zwischen allen zukünftigen Instanzen der Klasse geteilt. Verwendet man zwei Instanzen einer solchen fehlerhaften Klasse auf ein und der selben Seite, so führt dies unweigerlich zu schwer identifizierbaren Seiteneffekten.

  5. Im Hinblick auf Seiteneffekten bei Default-Werten für Objekte (siehe vorheriger Punkt), sollten Objekte am besten im Konstruktor initialisiert werden. Wenn Sie sich unsicher sind, dann verwenden Sie ganz einfach ausschließlich den Konstruktor:

    constructor: function () {
        this.title = 'Test';
        this.height = 200;
        this.width = 400;
        this.callParent(arguments);
    }

5. Mixins

In JavaScript kann man jederzeit eine Objekt um weitere Eigenschaften oder Methoden ergänzen. Mann kann sich hierbei einfach von den Eigenschaften oder Methoden eines anderen Objektes bedienen. Bei Funktionen, die als Konstruktor verwendet werden, weist man einfach die auszuleihenden Properties dem Prototype-Property des leihenden Objektes zu.

var verleiher = function();
    foo: function() {
        return "bar";
    }
}

var leiher = function() { };
leiher.prototype.foo = verleiher.foo;

var instanz = new leiher();
instanz.foo();

Eine vergleichbare (aber ausgefeiltere) Funktionalität bieten so genante "Mixins" in Ext Js. Alle Methoden und Eigenschaften werden dabei dem Prototyp-Objekt der neuen Klasse zugewiesen.

Ext.define('MyNamespace.TheAnswer', {

    whatToSay: 'Hello World ',

    getText: function() {
        return this.whatToSay + new Date().getFullYear();
    }
});

Ext.define('MyNamespace.HelloWindow', {
    extend: 'Ext.window.Window',
    require: [],
    mixins: ['MyNamespace.TheAnswer'],

    title: 'Test',
    height: 200,
    width: 400,

    constructor: function () {

        this.items = [{
            xtype: 'text',
            text: this.getText(),
        }];

        this.callParent(arguments);
    }
});

Ext.onReady(function () {

    var helloWindow = Ext.create('MyNamespace.HelloWindow');
    helloWindow.show();
});

Beispiel: Mixins

Ein Mixin mit denen man während der Entwicklung von Ext JS Komponenten unweigerlich in Kontakt treten wird ist übrigens Ext.util.Observable. Es bietet Methoden zum senden von Events und wird von allen Komponenten verwendet.

« zurück