Website-Trinity

HTML für den Aufbau, CSS3 für das Layout und JavaScript für die Logik
Kommentare

Die angedeutete Dreiteilung von Webseiten führt dazu, dass Sie nicht nur klare Zuständigkeiten haben, sondern auch jeden der drei Teile in jeweils separate Dateien auslagern können. Durch diese Maßnahme lässt sich Ihre Webapplikation besser strukturieren und wird dadurch wesentlich wartungsfreundlicher. Lesen Sie im Speziellen, welche Möglichkeiten der neue HTML5-Standard für Ihre JavaScript-Logik bietet und welche Problematiken bei der Umsetzung von Logik via JavaScript zu beachten sind.

Mit HTML5 ist für Webentwickler Umdenken angesagt. Mit diesem neuen Entwurf der Standardisierung soll HTML wieder zu dem werden, was es ursprünglich war: eine Auszeichnungssprache. Für Sie als Entwickler bedeutet das konkret, dass HTML nur noch verwendet werden sollte, um den Aufbau einer Seite zu beschreiben. Dafür wurden eine ganze Reihe neuer Tags wie eingeführt. Da HTML nun nur noch den Aufbau der Seite beschreibt, übernehmen die Cascading Stylesheets, mittlerweile in Version 3, kurz CSS3, das eigentliche Layouting und damit die Positionierung und das Aussehen der verschiedenen HTML-Elemente. Der dritte Bereich einer Webseite, die Logik, wird durch JavaScript abgedeckt. Logik bedeutet im Zusammenhang mit Webseiten mittlerweile nicht nur Validierung von Eingaben und das Senden von Daten über asynchrone XMLHttpRequests zum Server. Da die Rechner, auf denen die Webbrowser ausgeführt werden, immer leistungsstärker werden, können Sie im Frontend verschiedenste Berechnungen durchführen lassen und so Ihren Webserver auch teilweise etwas entlasten.

Die angedeutete Dreiteilung von Webseiten führt dazu, dass Sie nicht nur klare Zuständigkeiten haben, sondern auch jeden der drei Teile in jeweils separate Dateien auslagern können. Für Sie heißt das im Idealfall, dass Sie pro Datei lediglich eine Sprache verwenden. Also liegt Ihr HTML in .html-Dateien, das CSS der Seite in .css-Dateien und das JavaScript schließlich in .js-Dateien. Durch diese Maßnahme lässt sich Ihre Webapplikation besser strukturieren und wird dadurch wesentlich wartungsfreundlicher.

Aber nun zum eigentlichen Thema dieses Artikels: Welche Auswirkungen hat der neue HTML5-Standard auf die Art und Weise, wie Sie Ihr JavaScript schreiben? Wie auch bei HTML existieren auch in JavaScript verschiedene Versionen der zugrunde liegenden Standardisierung. Dieser Standard heißt ECMAScript. Durch ECMAScript wird der Sprachkern von JavaScript über die Browsergrenzen hinweg standardisiert. Wichtige Konzepte, die diesem Standard unterliegen, sind beispielsweise die Prototypenorientierung der Sprache statt einem klassenbasierten Ansatz, aber auch Klassen von Objekten wie Object, Array oder Date werden hier standardisiert. Alle modernen Browser, sogar der Internet Explorer in der Version 10, unterstützen mittlerweile den ECMAScript-Standard in der Version 5. Das heißt wiederum, dass Sie, wenn Sie lediglich moderne Browser in Ihrer Applikation unterstützen müssen, auf das umfangreiche Featureset von ECMAScript zurückgreifen können. Ist dies nicht der Fall, bleibt Ihnen immer noch die Alternative auf Frameworks zurückzugreifen, die diese Funktionalität innerhalb von JavaScript nachbauen und Ihnen zur Verfügung stellen.

Der Zweck von clientseitigem JavaScript ist, dass Sie auf Eingaben des Benutzers beziehungsweise auf Antworten des Servers reagieren und die dem Benutzer dargestellten Informationen auf eine gewisse Art modifizieren. Ein ganz einfaches Beispiel, das das Zusammenspiel von HTML und JavaScript recht gut verdeutlicht, ist die Progressbar. Dieses Element bietet Ihnen die Möglichkeit, einen Fortschrittsbalken auf Ihrer Seite einzubinden. Sie können dieses Element verwenden, um Ihren Nutzern den Fortschritt von Operationen, wie beispielsweise einer Berechnung, eines Uploads oder ähnlichem, anzuzeigen. Das Progresselement in reinem HTML ist recht statisch und hat in den meisten Fällen die Attribute max, das den Wert für 100 Prozent enthält und value, das den aktuellen Wert enthält. Möchten Sie die Progressbar animieren und dem Benutzer Ihrer Seite damit den Fortschrittsverlauf visualisieren, müssen Sie den Wert des value-Attributes anpassen. Und genau an dieser Stelle kommt JavaScript ins Spiel. In Listing 1 sehen Sie den Quellcode dieses Beispiels.

    

        var interval,
            progressBar = function () {
                var progress = document.getElementById('progress'),
                    value = parseInt(progress.value);

                if (value === parseInt(progress.getAttribute('max'))) {
                    clearInterval(interval);
                } else {
                    progress.value = value + 1;
                }
            };

        var interval = setInterval(progressBar, 100);
    

Das HTML-Element des Fortschrittsbalkens wird mit einer id, dem Attribut max mit dem Wert 100 und einem Startwert von 0 ausgestattet. Im zweiten Schritt führen Sie mit setInterval eine JavaScript-Funktion in regelmäßigen Abständen so lange aus, bis der Fortschrittsbalken seinen Maximalwert erreicht hat. Ist dieser Wert erreicht, wird die Ausführung der Funktion mit clearInterval für die Zukunft verhindert.

Am Beispiel der Progressbar sehen Sie noch eine weitere Eigenschaft vieler HTML5-Elemente. Sie lassen sich kaum durch CSS verändern. Bei einem einfachen Fortschrittsbalken ist das noch nicht so problematisch, haben Sie allerdings für Ihre Seite ein striktes Farbschema, kann sogar das progress-Element zu einem Problem werden. Noch deutlicher wird dies, wenn Sie das Eingabeelement für Datumsangaben in Form von <input type=’date’… verwenden. Unterstützt Ihr Browser dieses Element, wird Ihnen ein Datepicker präsentiert. Das Aussehen dieses Datepickers können Sie allerdings nicht beeinflussen. Falls dies ein kritisches Element auf Ihrer Seite ist, bei dem es darauf ankommt, dass es im Stil Ihrer Seite angezeigt wird, bleibt Ihnen nichts anderes übrig als, wie bisher auch, auf existierende Bibliotheken zurückzugreifen, die die gleiche Funktionalität bieten wie die HTML5-Elemente auch.

Neben zahlreichen Elementen, mit denen Sie über JavaScript interagieren können, stehen Ihnen allerdings auch noch einige weitere Programmierschnittstellen für JavaScript zur Verfügung, die nicht direkt mit der Interaktion mit HTML-Elementen zu tun haben. Diese Schnittstellen erweitern den Sprachkern von JavaScript um weitere nützliche Funktionalitäten im Web- und Mobilebereich und tragen damit der aktuellen Entwicklung Rechnung. Webapplikationen werden nicht mehr nur mit Webbrowsern auf Desktoprechnern verwendet. Stattdessen verfügen mittlerweile zahlreiche Geräte vom Mobiltelefon über Tablet-Computer bis hin zum Fernseher über Browser, die in der Lage sind, HTML-Seiten mit CSS und JavaScript auszuführen.

Vor allem im Bereich mobiler Clients ist die Positionsbestimmung relevant. Innerhalb Ihrer Webapplikation steht Ihnen das Geolocation-API zu diesem Zweck zur Verfügung.

Geolocation

Geolocation dient, wie der Name bereits andeutet, zur Positionsbestimmung des Benutzers beziehungsweise der Bestimmung des Standorts des Geräts, mit dem der Benutzer die Webapplikation verwendet. Die Positionsbestimmung erfolgt anhand von Längen- und Breitengraden. Die Quelle der Positionsbestimmung ist vor allem GPS, aber auch andere Angaben, wie die IP-Adresse, der Standort eines öffentlichen WLANs und ähnliches, können zur Bestimmung der aktuellen Position herangezogen werden.

Die Positionsbestimmung ist jedoch nicht nur auf mobile Endgeräte beschränkt. Auch im Webbrowser auf Ihrem Rechner zu Hause oder im Büro können Sie lokalisiert werden, auch wenn die Positionsbestimmung von mobilen Geräten mit GPS-Chip bei weitem exakter ist. Bei Desktoprechnern und Notebooks muss auf Alternativen zum GPS zurückgegriffen werden. Wenn Sie sich beispielsweise auf einer Konferenz befinden und die Hardware ein paar Tage zuvor in einer anderen Stadt verwendet wurde und dem dortigen Standort zugewiesen wurde, kann es durchaus vorkommen, dass Sie statt an Ihrem aktuellen Standort in einer Stadt viele hundert Kilometer entfernt lokalisiert werden.

Zu einer weiteren Schwierigkeit bei der Positionsbestimmung kommt es, wenn Sie die Datei mit dem Quellcode zur Positionsbestimmung statt über HTTP, also dem Präfix http://, über das file-Protokoll, also file://, ausliefern. Manche Browser blockieren solche Anfragen nach der Position von Code aus diesen Quellen komplett.

Damit nicht jeder Webserver Ihre aktuelle Position auslesen, speichern und im schlimmsten Fall ein detailliertes Bewegungsprofil von Ihnen erstellen kann, haben Sie die Möglichkeit, Ihren Browser so zu konfigurieren, dass er entweder keinerlei Angaben zur aktuellen Position macht, dass er beim Zugriff auf die aktuelle Position erst nachfragt, oder dass Sie Ihre Position für bestimmte Domains freigeben. In der Praxis sieht eine solche Nachfrage so aus, dass der Browser ein kleines Nachrichtenfenster öffnet, in dem Sie gefragt werden, ob Sie Ihre aktuelle Position für die aktuell angezeigte Seite freigeben möchten. Erst wenn Sie diese Nachfrage bestätigen, bestimmt Ihr Webbrowser Ihre aktuelle Position und Sie können über das navigator.geolocation-Objekt auf die Position zugreifen.

Das Objekt navigator.geolocation verfügt über die Methode getCurrentPosition. Diese Methode holt sich asynchron die notwendigen Informationen über die aktuelle Position und ruft eine von zwei Callback-Funktionen auf, sobald die Daten vorliegen. Die Methode getCurrentPosition akzeptiert zwei Werte, der erste ist eine Funktion, die im Erfolgsfall ausgeführt wird, der zweite Wert ist eine Funktion, die im Fehlerfall ausgeführt wird.

Der Fehler-Callback kann von Ihnen verwendet werden, um den Benutzer darüber zu informieren, dass zum Beispiel die Abfrage der Position durch den Benutzer nicht freigegeben wurde.

Die Callback-Funktion, die im Erfolgsfall ausgeführt wird, erhält ihrerseits wiederum ein Positionsobjekt. Über dieses Objekt können Sie innerhalb der Callback-Funktion auf die Position des Benutzers zugreifen. Das Geolocation-API gibt Ihnen als Informationen über die Position lediglich Koordinatenangaben. Falls Sie Ihren Benutzern nicht nur ihren aktuellen Standpunkt in Zahlen ausdrücken möchten, sollten Sie Dienste wie Google-Maps verwenden, um die Position zu visualisieren und die Koordinaten weiter zu verarbeiten.

Listing 2 zeigt Ihnen, wie Sie Geolocation verwenden können. Dieses Beispiel sorgt lediglich dafür, dass die aktuelle Position ausgelesen wird und gibt die beiden Positionswerte für Längengrad und Breitengrad auf der Konsole aus. Außerdem wird der Wert für accuracy ausgegeben. Dieser zeigt, wie genau die Angaben sind. accuracy gibt in Metern an, wie groß die Abweichung sein kann.

var success = function (position) {
    console.log('Länge: ' + position.coords.longitude);
    console.log('Breite: ' + position.coords.latitude);
    console.log('Abweichung: ' + position.coords.accuracy);
}

var error = function (msg) {
    console.log(msg);
}

navigator.geolocation.getCurrentPosition(success, error);

Neben der Positionsbestimmung stehen Ihnen jedoch auch noch weitere Schnittstellen zur Verfügung, die eines der großen Probleme von clientseitiger Logik versuchen zu lösen: das Zwischenspeichern von Daten.

IndexedDB

Mit der IndexedDB steht Ihnen bei der Entwicklung von Webapplikationen eine vollwertige Datenbank auf Clientseite zur Verfügung. Bevor Sie die Datenbank verwenden können, muss diese initialisiert werden. Hierfür gibt es einen sehr einfachen Trick: Beim Öffnen der Datenbankverbindung mithilfe der open-Methode des IndexedDB-Objekts können Sie eine Versionsnummer angeben. Existiert die Datenbank in dieser Version noch nicht auf dem aktuellen Client, wird eine Callback-Funktion, die Sie unter der Eigenschaft onupgradeneeded angeben, ausgeführt. Hier können Sie die Struktur der verschiedenen ObjectStores angeben. Ein ObjectStore hat stets einen Namen und einen so genannten keyPath, also eine Eigenschaft der Datensätze, die diese identifiziert. Außerdem können Sie durch Aufrufe der Methode createIndex auf den ObjectStore zusätzliche Indizes definieren. In Listing 3 sehen Sie, wie Sie eine Datenbank entsprechend initialisieren können.

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

var request = window.indexedDB.open("MyTestDatabase", 3);

request.onerror = function (event) {
    console.log(event.target.errorCode);
}

request.onsuccess = function (event) {
    console.log('Initialized');
}


request.onupgradeneeded = function(event) {
    var db = event.target.result,
        objectStore = db.createObjectStore("customers", { keyPath: "id" });

    objectStore.createIndex("name", "name", { unique: false });
    objectStore.createIndex("email", "email", { unique: true });

    /*for (var i in initialData) {
        objectStore.add(initialData[i]); 
    }*/
};

Ist die Initialisierung durchgeführt, können Sie die Datenbank in Ihrer Applikation verwenden. Die Operationen, die Ihnen dabei zur Verfügung stehen, sind: bestehende Elemente auslesen, neue Elemente hinzufügen und bestehenden Elemente löschen. Sämtliche Operationen passieren aufgrund einer Transaktion. Diese starten Sie mittels der transaction-Methode des Datenbankobjekts. Um die Transaktion korrekt zu starten, müssen Sie beim Methodenaufruf angeben, welche ObjectStores die Transaktion umfassen soll und welche Art von Operation Sie durchführen möchten, also ob Sie nur lesend oder auch schreibend auf die Datenbank zugreifen möchten. Innerhalb dieser Transaktion können Sie dann auf die einzelnen ObjectStores zugreifen. Das Erstellen neuer Elemente erfolgt über die Methode add eines ObjectStores. Als Argument übergeben Sie dieser Methode die Objektrepräsentation eines Datensatzes. Als Rückgabewert dieser Methode erhalten Sie ein Objekt, das die Eigenschaften onsuccess und onerror aufweist, denen Sie entsprechende Callback-Funktionen zuweisen können, die nach erfolgter Operation beziehungsweise im Fehlerfall aufgerufen werden. Gleiches gilt für die gesamte Transaktion. Auch hier können Sie über die Eigenschaften oncomplete und onerror Callback-Funktionen definieren.

Das Löschen von Daten erfolgt analog zum Hinzufügen. Hier steht Ihnen innerhalb des ObjectStores die Methode delete zur Verfügung. Sie akzeptiert den Wert des Schlüssels des zu entfernenden Datensatzes. Auch hier haben Sie die Möglichkeit, Callback-Funktionen für onsuccess und onerror zu definieren.

Die eingefügten Daten können Sie einerseits mit einem Aufruf der get-Methode auf einen ObjectStore auslesen, indem Sie den Schlüssel eines Datensatzes als Argument übergeben oder Sie nutzen einen zuvor von Ihnen definierten Index und lesen Daten anhand eines Indexwertes aus der Datenbank aus. Auch hier sollten Sie alle weiterführenden Operationen innerhalb des onsuccess-Callbacks ausführen. Über das Objekt, das Sie als Argument erhalten, können Sie über den Wert target.result auf das Ergebnis der Abfrage zugreifen.

In Listing 4 sehen Sie die verschiedenen Operationen für das Hinzufügen, Löschen und Auslesen von Daten in einem Codeblock.

objectStore.get('4').onsuccess = function (event) {
    console.log('Name: ' + event.target.result.name);
};

objectStore.delete('5').onsuccess = function () {
    console.log('removed');
}


objectStore.index('name').get('Donna').onsuccess = function (event) {
    console.log("ID: " + event.target.result.id);
};

Neben den hier vorgestellten Features verfügt IndexedDB noch über zahlreiche weitere Features. So können Sie beispielsweise mithilfe eines Cursors über sämtliche Elemente eines ObjectStores iterieren oder mit der Methode deleteDatabase eine komplette IndexedDB-Datenbank wieder von Ihrem Client entfernen.

Mit IndexedDB erhalten Sie auf Clientseite eine sehr umfangreiche Möglichkeit, mit der Sie jeweils mehrere gleichartige Objekte in ObjectStores speichern können. Die Struktur und der Inhalt der gespeicherten Objekte werden dabei nicht vom Browser vorgeschrieben. Ihnen steht es somit frei, wie Sie die einzelnen ObjectStores aufbauen.

Die Schattenseite dieser Schnittstelle ist, dass durch sie die Komplexität Ihrer Applikation im Frontend weiter anwächst, da Sie sich sowohl auf Server- als auch auf Clientseite um die Persistierung und Synchronisierung von Daten in einer Datenbank kümmern müssen.

Des Weiteren sollten Sie darauf achten, dass Datenbanken Speicherplatz benötigen. Wenn Sie nur einzelne Objekte speichern, die über wenige Eigenschaften mit kleinen Zeichenketten als Werte verfügen, ist das weniger ein Problem. Sollten Sie allerdings größere Datenmengen speichern wollen, kann es hier auch durchaus zu Problemen kommen. Firefox fragt beispielsweise beim Benutzer nach, wenn eine Webapplikation versucht, Blob-Daten mit einer Größe von über 50 MB zu speichern. Ansonsten begrenzt Firefox den maximalen Speicher für eine Datenbank nicht. Chrome hingegen limitiert den maximalen Speicherplatz, der zur Verfügung steht, relativ zum insgesamt verfügbaren Speicherplatz.

Der Vorteil von IndexedDB ist, dass Ihre gespeicherten Daten auch den Neustart des Browsers beziehungsweise sogar den Neustart des Rechners überdauern und erst gelöscht werden, wenn entweder der Benutzer über die Konfiguration seines Browsers die Daten manuell löscht oder Sie innerhalb der Applikation dafür sorgen, dass die Datenbank gelöscht wird.

Auch für die Sicherheit der Daten ist gesorgt, indem nicht domainübergreifend auf die Datenbanken zugegriffen werden kann. Das bedeutet, dass Sie nicht von einer Webapplikation aus Domain A auf die Daten einer Datenbank aus Domain B zugreifen können, obwohl Sie deren Namen und Aufbau kennen.

Der nächste Abschnitt dieses Artikels beschäftigt sich mit Web Storage – einem weiteren Mechanismus, mit dem Sie Daten auf Clientseite zwischenspeichern können.

Web Storage

Wem IndexedDB als Speicherart im Client zu schwergewichtig ist, der kann auf Web Storage zurückgreifen. Web Storage ist ein einfaches System zur Speicherung von Schlüsselwertpaaren. Im Zusammenhang mit Web Storage stehen Ihnen zwei globale Objekte zur Speicherung von Daten zur Verfügung: Zum einen localStorage und zum anderen sessionStorage. Der Unterschied liegt darin, dass localStorage langlebiger ist und den Neustart des Browsers überdauert. Die Daten in sessionStorage hingegen stehen Ihnen lediglich für die aktuelle Browsersitzung zur Verfügung und werden beim Beenden des Browsers gelöscht. Die Schnittstelle beider Speichervarianten ist allerdings identisch. Listing 5 zeigt Ihnen die verschiedenen Methoden, die Ihnen im Rahmen von Web Storage zur Verfügung stehen.

console.log('Elements: ' + localStorage.length);
localStorage.setItem('1', 'Firefox');
localStorage.setItem('2', 'Chrome');
console.log('Elements: ' + localStorage.length);
console.log('Element 2: ' + localStorage.getItem('1'));
console.log('Key 2: ' + localStorage.key(1));
localStorage.removeItem('2');
console.log('Elements: ' + localStorage.length);
localStorage.clear();
console.log('Elements: ' + localStorage.length);

Die Eigenschaft length gibt an, wie viele Elemente sich aktuell im jeweiligen Speicher befinden. Mit der setItem-Methode können Sie einen Schlüssel und einen Wert angeben, den Sie speichern möchten. Über die getItem-Methode und den Schlüssel können Sie den jeweiligen Wert wieder auslesen. Benötigen Sie einen Wert nicht mehr, können Sie diesen mit removeItem und dem Schlüssel aus dem Speicher entfernen. Rufen Sie setItem mit einem bereits bestehenden Schlüssel auf, wird der bestehende Wert durch den neuen Wert ersetzt. Dadurch überschreiben Sie quasi einen Eintrag im Speicher. Den Schlüssel eines Elements können Sie mit der key-Methode herausfinden. Sie müssen hier lediglich angeben, für das wievielte Element Sie den Schlüssel auslesen möchten. Mit dieser Methode und der Anzahl der Elemente im Speicher können Sie sich einen Iterator schaffen, mit dessen Hilfe Sie sämtliche Elemente des Speichers auslesen können.

Möchten Sie den gesamten sessionStorage oder localStorage leeren, erreichen Sie dies durch einen Aufruf der clear-Methode. Objekte, die gespeichert werden, werden zuerst in eine Zeichenkette umgewandelt. Das bedeutet für Sie als Entwickler, dass Sie dafür sorgen sollten, dass Ihre Objekte die toString-Methode korrekt implementieren und so eine ordnungsgemäße Repräsentation Ihres Objekts erstellt wird, die eine Wiederherstellung nach dem Auslesen aus dem Speicher erlaubt.

Pro Origin steht Ihnen jeweils ein abgeschirmtes localStorage und sessionStorage-Objekt zur Verfügung. Damit ist die Sicherheit und Integrität der Daten im Speicher sichergestellt. Geben Sie auf der Konsole Ihres Browser lediglich das Speicherobjekt an, erhalten Sie eine Repräsentation des Speichers mit sämtlichen darin liegenden Schlüsseln und Werten.

Zusätzlich zu den beiden Mechanismen, um Daten clientseitig in Datenbanken zu speichern, existiert noch eine Möglichkeit zum Umgang mit Dateien innerhalb des Browsers. Der nächste Abschnitt beschäftigt sich mit dem File-API.

File-API

Trotz des File-APIs bleibt der Webbrowser, was er ist: ein Sandkasten für Webapplikationen. Das bedeutet, trotz seines vielversprechenden Namens bietet Ihnen das File-API keinen vollständigen Zugriff auf das Dateisystem des Rechners, auf dem der Webbrowser ausgeführt wird. Das wäre ein zu großes Sicherheitsrisiko. Stattdessen können Sie als Nutzer eines Webbrowsers Dateien auswählen und mithilfe des File-APIs verschiedene Operationen mit diesen Dateien durchführen. Listing 6 zeigt, wie Sie das File-API in Ihre Webapplikation einbinden können.

var handleFiles = function (files) {
    console.log(files[0]);

    var reader = new FileReader();

    reader.readAsText(files[0]);

    reader.onload = function () {
        console.log(reader.result)
    }
}

Die Grundlage für das File-API bildet ein Input-Element vom Typ file. Über das onchange-Event wird eine Callback-Funktion an das Element gebunden, die aufgerufen wird, sobald der Benutzer eine Datei ausgewählt hat. Das Argument, das in diesem Fall der Callback-Funktion übergeben wird, this.files, ist ein Objekt vom Typ FileList. Es enthält Informationen über die ausgewählten Dateien. Auf die erste Datei in dieser Liste greifen Sie über den Index 0 zu. In erster Linie erhalten Sie hier einige Metainformationen wie beispielsweise das Datum der letzten Änderung, den Namen, den Typ und die Größe der Datei.

Mit der length-Eigenschaft der FileList können Sie einen Iterator implementieren, mit dessen Hilfe Sie über die Liste iterieren können.

Neben der FileList und den darin enthaltenen File-Objekten existiert die Klasse FileReader. Mit Objekten dieses Typs sind Sie in der Lage, Dateien auszulesen und mit ihnen zu arbeiten. Wenn Sie eine Instanz des FileReaders generieren, können Sie mit der Methode readAsText Textdateien auslesen. Dafür übergeben Sie das File-Objekt der gewünschten Datei an die Methode. Die Bearbeitung findet asynchron statt, das bedeutet, dass Ihnen der Inhalt nicht sofort zur Verfügung steht. Um mit dem Dateiinhalt arbeiten zu können, müssen Sie der onload-Eigenschaft des Readers eine Callback-Funktion zuweisen, innerhalb derer Sie dann auf die entsprechenden Informationen zugreifen können. Sobald diese Funktion aufgerufen wird, steht Ihnen in der result-Eigenschaft des Readers der Inhalt der Datei zur Verfügung.

Mit den verschiedenen Features des File-APIs können Sie neben dem Auslesen des Inhalts von Dateien auch Uploads von Dateien selbst in die Hand nehmen und hier beispielsweise einen Fortschrittsbalken für den Upload oder ähnliches mit einbauen.

Neben der Datenhaltung und dem Dateizugriff ändern sich auch die Schnittstellen für zeitintensive Berechnungen mit den neuen JavaScript-APIs.

Web Workers

Nutzen Sie konsequent die Möglichkeit, mit JavaScript Logik auf Clientseite umzusetzen, werden Sie früher oder später auf ein Problem stoßen: Sobald eine Operation in JavaScript umfangreicher wird, kann es passieren, dass der Browser Ihres Nutzers langsamer bis überhaupt nicht mehr reagiert. Mit dem Web-Workers-Feature können Sie derartige Operationen allerdings aus dem direkten Browserkontext herauslösen, die Berechnung separat durchführen und das Ergebnis in den Browser zurücklaufen lassen.

Als konkretes Beispiel für die Anwendung von Web-Workers sehen Sie nun, wie Sie Primzahlen in einem Worker berechnen können und das Ergebnis in den Haupt-Thread zurückliefern und diese dort anzeigen. Mehr dazu in Listing 7.


    Höchste Primzahl: 

    
        new Worker('prime.js').onmessage = function (msg) {
            document.getElementById('result').innerHTML = msg.data;
        }
    

// prime.js
var i, n = 1, results = 1;
outerLoop: while (true) {
    n += 1;
    for (i = 2; i <= Math.sqrt(n); i += 1) {
        if (n % i === 0) {
            continue outerLoop;
        }
    }
    self.postMessage(n);
    results += 1;
    if (results == 1000) self.terminate();
}

Der Start eines Workers ist relativ einfach. Sie müssen lediglich ein neues Objekt vom Typ Worker mithilfe des new-Operators erzeugen. Außerdem müssen Sie dem Konstruktor den Namen der Datei mitgeben, die den Quellcode enthält, der im Worker ausgeführt werden soll. Mit dem neu erstellten Worker-Objekt können Sie dann weiterarbeiten. Es besitzt eine Eigenschaft mit dem Namen onmessage. Diese Eigenschaft akzeptiert als Wert eine Callback-Funktion, die ausgeführt wird, sobald der Worker Daten sendet. Im Falle des Primzahlenbeispiels ist dies jeweils die gefundene Primzahl. Die Callback-Funktion erhält als Argument ein Objekt, in dessen data-Eigenschaft die Nachricht des Workers enthalten ist.

Der Worker wiederum kann mit dem Haupt-Thread kommunizieren, indem die Methode self.postMessage ausgeführt wird. Dieser Methode können Sie eine Zeichenkette oder ein JSON-Objekt übergeben, auf die Sie dann, wie bereits erwähnt, über data zugreifen können.

Die Kommunikation ist allerdings nicht nur unidirektional vom Worker in den Haupt-Thread möglich. Auch der Haupt-Thread kann nach dem Start aktiv mit dem Worker kommunizieren. Auch hier steht Ihnen die Methode postMessage zur Verfügung, die Sie auf dem Worker-Objekt aufrufen können. Auf der anderen Seite können Sie im Worker mit self.onmessage wiederum eine Callback-Funktion registrieren, mit der Sie auf eingehende Nachrichten vom Haupt-Thread reagieren können.

Den Workerprozess können Sie auch aktiv wieder beenden, indem Sie die terminate-Methode aufrufen. Sie können diese Methode sowohl aus dem Haupt-Thread auf dem Worker-Objekt als auch aus dem Worker direkt über den Aufruf von self.terminate ausführen.

Mit diesen Methoden können Sie eine Infrastruktur schaffen, in der Sie die meisten ressourcenintensiven Berechnungen nicht mehr im Haupt-Thread durchführen müssen, sondern mit den Worker-Threads die Möglichkeit haben, potenziell blockierende Operationen aus diesem Kontext herauszulösen. Durch die Möglichkeit in beide Richtungen zu kommunizieren, also sowohl vom Worker als auch vom Haupt-Thread aus initiiert, ist es auch nicht notwendig, einen Worker nach einer Berechnung wieder zu beenden. Der Worker läuft parallel weiter und der Haupt-Thread kann bei Bedarf eine neue Berechnung durch das Versenden einer Nachricht anstoßen.

Fazit

Wie sieht die Realität in Bezug auf die JavaScript-APIs aus? Das größte Problem mit HTML5 und auch mit den hier behandelten JavaScript-APIs ist, dass diese nicht von allen Browsern unterstützt werden. Haben Sie eine kontrollierte Umgebung, in der Sie wissen, welche Browser auf Ihre Applikation zugreifen, können Sie auch moderne Features gefahrlos nutzen. Ist dies nicht der Fall, müssen Sie auf jeden Fall dafür sorgen, dass Ihre Applikation für sämtliche Nutzer verwendbar ist. Im Endeffekt müssen Sie, wenn Sie möglichst viele verschiedene Browser unterstützen wollen, Weichen in Ihre Applikation einbauen. Sie sorgen einerseits dafür, dass die Objekte, die die Schnittstellen bieten, gleichgeschaltet werden, so heißt IndexedDB beispielsweise in Firefox-Browsern „mozIndexedDB“ und in Chrome „webkitIndexedDB“. Mit einer einfachen Zeile Quellcode, die Sie in Listing 8 sehen können, können Sie dafür sorgen, dass Ihnen unter indexedDB browserübergreifend diese Funktionalität zur Verfügung steht:

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

Der zweite Effekt einer derartigen Weiche ist, dass Browser, die diese Features nicht unterstützen, entweder eine Fehlermeldung ausgeben oder eine andere, vergleichbare Funktionalität verwenden.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -