Open Source im professionellen Einsatz
Linux-Magazin 12/2014
1778

Beispielanwendung

Abbildung 2 zeigt die Beispielanwendung [6] in der Browseransicht von Firefox unter Ubuntu 12.04. Der Report fragt die Erdbeben der letzten Tage im Internet ab. Sortiert nach deren Entfernung auf der Erdoberfläche vom Standort stellt er sie tabellarisch und aufsteigend dar. Über die beiden Textfelder am oberen Ende der Seite wählt der Nutzer den Standort. Die Anwendung interpretiert Längen- und Breitengrad im Gradmaß.

Abbildung 2: Der Report zeigt die Erdbeben der letzten Tage für den Standort Reykjavík.

Ein Klick auf den Knopf mit der Aufschrift »Aktueller Standort« ermittelt den Standort per Geolocation-API – vorausgesetzt der Benutzer stimmt dem zu. Dann trägt er diesen in die vorgesehenen Textfelder ein. Ändert sich ein Wert, stellt die Anwendung den Report neu zusammen, den ein Webserver ausliefert, der zudem in der Lage ist, CGIs auszuführen.

Abbildung 3 zeigt, ebenfalls in der Browseransicht von Firefox, die Erdbebendaten im Json-Format. Aufgrund der Cross Origin Resource Sharing Policy [9] des Anbieters Earthquake Report [10] lädt der Browser die Daten nicht direkt, sondern schaltet gemäß Abbildung 4 das Python-Skript aus Listing 3 dazwischen. Wird der Report angefordert (mit der Ziffer 1 in Abbildung 4 markiert) startet der Webserver das Python-Skript über seine CGI-Schnittstelle. Dafür sorgt die Zeile 1 von Listing 3, die den Python-Interpreter als ausführende Instanz etabliert.

Listing 3

get_data.py

01 #!/usr/bin/env python
02 import urllib2
03
04 print 'Content-Type: application/json\r\n\r\n',
05 print urllib2.urlopen("http://earthquake-report.com/feeds/recent-eq?json").read(),
Abbildung 3: Die Anwendungsdaten liegen bereits im praktischen Json-Format vor.
Abbildung 4: Das Wrapperskript get_data.py holt Daten aus dem Internet.

Zeile 2 bindet das in Zeile 5 benötigte Python-Modul »urllib2« ein. Zeile 4 schreibt die erforderliche Mimetype-Angabe über den Befehl »print« in die Standardausgabe, hier »application/json« . Zeile 5 liest anschließend die Erdbebendaten unter der angegebenen URL, um sie analog zur vorherigen Zeile auszugeben. Der Webserver liefert die in die Standardausgabe geschriebenen Daten als Antwort an den Report zurück (mit Ziffer 3 in Abbildung 4 markiert).

Die eigentliche Beispielanwendung startet nach dem Aufruf der HTML-Datei aus Listing 4 im Browser. Der zugehörige Code bindet in den Zeilen 3 bis 11 alle nötigen Dateien ein. Dazu gehören das React-Framework (Zeile 6), der JSX-Übersetzer (Zeile 7), Jquery (Zeile 8), die Anwendung selbst (Zeile 9) sowie einige Hilfsfunktionen (Zeile 10). Der Wert »text/jsx« im Type-Attribut aus Zeile 9 veranlasst den JSX-Übersetzer dazu, die Datei »report.js« im Vorübergehen zu übersetzen. Im Produktiveinsatz sollten Entwickler Dateien mit JSX-Code zuvor besser über das Node-Modul »jsxc« [11] übersetzen.

Listing 4

index.html

01 <!DOCTYPE html>
02 <html>
03 <head>
04     <title>Erdbeben-Report</title>
05     <link rel="stylesheet" href="css/app.css"/>
06     <script src="http://fb.me/react-0.11.1.js"></script>
07     <script src="http://fb.me/JSXTransformer-0.11.1.js"></script>
08     <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
09     <script type="text/jsx" src="js/report.js"></script>
10     <script src="js/utilities.js"></script>
11 </head>
12 <body></body>
13 </html>

Nach dem Initialisieren lädt die App mit Hilfe der Jquery-Methode »getJSON()« die Erdbebendaten (Listing 5). Der Methodenaufruf übernimmt im ersten Parameter die URL des Wrapperskripts »get_data.py« und im zweiten die Rückruffunktion, die er nach dem erfolgreichen Laden der Json-Daten aufruft. Letztere speichert das Javascript-Objekt, das durch Parsen des geladenen Json-Dokuments entsteht, in der Variablen »data« und reicht das Objekt dann in Zeile 3 an die React-Methode »renderComponent()« weiter. Diese ruft nun ihrerseits die Renderfunktion der Komponente »Report()« auf und gibt den Report im Browser aus.

Listing 5

report.js (Auszug)

01 $(document).ready(function() {
02         $.getJSON("./cgi-bin/get_data.py", function(data) {
03                 React.renderComponent(<Report items={data}/>, document.body);
04         });
05 });

Die React-Komponente »Report()« hatte bereits in Listing 2 einen Auftritt. Sie enthält neben der Renderfunktion (Zeile 12) die beiden Methoden »getInitialState()« (Zeile 2) und »handleUserInput()« (Zeile 7). Erstere ruft React intern auf, sie übergibt die Anfangswerte der Statusvariablen. Zeile 4 wählt den anfänglichen Standort aus. Das ist in diesem Fall die isländischen Hauptstadt Reykjavík, die nur wenige Kilometer vom aktiven Vulkan Bardabunga entfernt liegt.

Die Kind-Komponente »ReportOptions« verwendet die Methode »handlerUserInput()« (Listing 2, Zeile 7), um Standorte zu verschieben. Sie übernimmt im ersten Parameter den Breiten- und im zweiten den Längengrad. Nach dem Speichern über »setState« (Zeile 8) erstellt die Anwendung den Report neu.

»Report« bindet in den Zeilen 16 bis 19 von Listing 2 die Komponente »ReportOptions« ein und übergibt ihr den aktuellen Standort und die Methode »handleUserInput()« . In den Zeilen 20 bis 23 integriert »Report« anschließend mit »ReportTable« eine weitere Komponente und reicht ihr ebenfalls den aktuellen Standort weiter (Zeile 21). Zeile 22 empfängt dann die Anwendungsdaten, die Zeile 3 in Listing 5 generiert.

Listing 6 zeigt die Komponente »ReportOptions« , die der Entwickler in den Zeilen 16 bis 19 von Listing 2 eingebunden hat. Ihre Renderfunktion ab Zeile 32 in Listing 6 erzeugt in den Zeilen 36 bis 42 sowie 44 bis 50 die Textfelder zur Auswahl des Standorts und zusätzlich den Button, über den die Anwendung den Standort des Users ermittelt. Die Textfelder lesen ihre Werte über das Attribut »value« aus den Eigenschaften »this.props.position.latitude« (Zeile 38) beziehungsweise »this.props.position.longitude« (Zeile 46).

Listing 6

report.js (Auszug)

01 var ReportRow = React.createClass({
02     render: function() {
03     return (
04         <tr>
05             <td>{dateFormatString(this.props.item.date_time)}</td>
06             <td><a href={this.props.item.link}>{strUpperWords(this.props.item.location)}</a></td>
07             <td>{Math.round(this.props.item.distance)}</td>
08             <td>{datePassedHours(this.props.item.date_time)}</td>
09             <td>{this.props.item.magnitude}</td>
10             <td>{Math.round(this.props.item.depth)}</td>
11         </tr>
12         );
13     }
14 });
15 [...]
16 var ReportOptions = React.createClass({
17     handleChange: function() {
18         this.props.onUserInput(<
19             this.refs.latitude.getDOMNode().value,
20             this.refs.longitude.getDOMNode().value
21         );
22     },
23     handlePosition: function() {
24         var that = this;
25         navigator.geolocation.getCurrentPosition(function(pos) {
26             that.props.onUserInput(
27                     Math.round(pos.coords.latitude*100)/100,
28                     Math.round(pos.coords.longitude*100)/100
29             );
30         }, function(err) { console.log(err) });
31     },
32     render: function() {
33         return (
34             <form>
35                 Breitengrad:&nbsp;
36                 <input
37                     type="number"
38                     value={this.props.position.latitude}
39                     ref="latitude"
40                     step="any"
41                     onChange={this.handleChange}
42                 />
43                 &nbsp;Längengrad:&nbsp;
44                 <input
45                     type="number"
46                     value={this.props.position.longitude}
47                     ref="longitude"
48                     step="any"
49                     onChange={this.handleChange}
50                     />
51                 &nbsp;
52                 <input
53                         type="button"
54                         value="Aktueller Standort"
55                         ref="postion"
56                         onClick={this.handlePosition}
57                 />
58             </form>
59         );
60     }
61 });

Ändert der Benutzer den Wert eines Textfelds, ruft React über das Attribut »onChange« (Zeilen 41 und 49) die Rückruffunktion »handleChange()« (Zeile 17) auf. Die holt sich die geänderten Werte jeweils über das Attribut »value« und die Methode »getDOMNode()« und reicht sie an »handlePosition()« (in Listing 2, Zeile 7) weiter.

Das Attribut »ref« in Listing 6 (Zeilen 39 und 47) erzeugt die Referenzen »this.refs.latitude« (Zeile 19) sowie »this.refs.longitude« (Zeile 20). Ein Klick auf den Knopf (Zeile 52) ruft die Methode »handlePosition()« ab Zeile 23 auf. Sie kommt als Rückruffunktion zum Einsatz, sobald der Nutzer den Knopf drückt, was das Attribut »onClick« in Zeile 56 bemerkt. Im Erfolgsfall reicht die asynchrone Methode »getCurrentPosition()« (Zeile 25) innerhalb von »handlePosition()« die Koordinaten in der Variablen »pos« weiter.

Die gerundeten Werte übergibt Listing 2 in den Zeilen 7 bis 11 an »handleUserInput()« . Die Komponente »ReportTable« aus Listing 1 steckt die Anwendungsdaten in eine Tabelle. Über »geoSort()« in Zeile 5 sortiert die Renderfunktion die Datensätze nach ihrer Entfernung zum Standort. Anschließend ruft sie über die »forEach« -Schleife die Komponente »ReportRow« (Zeile 7) auf und generiert für jeden Datensatz eine eigene Tabellenzeile. Ebenfalls in Zeile 7 hängt die Funktion »geoSort()« die HTML-Fragmente an das Feld »rows« . Zeile 6 berechnet zuvor noch den Abstand zwischen dem Datensatz und dem Standort.

Listing 6 zeigt die Komponente »ReportRow« (Zeile 1). Sie erzeugt für einen Datensatz aus »this.props.item« je eine Tabellenzeile im HTML-Format. Die Tabelle enthält die Spalten Datum, Ort, räumliche und zeitliche Entfernung, Magnitude sowie Tiefe des Erdbebens.

Fluxbau

Flux ist ein Entwurfsmuster für React-Anwendungen und stattet diese mit einem gerichteten, kreisförmigen Datenfluss aus (Abbildung 5). Flux zerlegt eine Anwendung dazu in vier Teile, die unterschiedliche Funktionen besitzen. Nach dem Muster zerlegt Flux auch den Javascript-Code und speichert ihn wie in Listing 7.

Listing 7

Von Flux erzeugte neue Verzeichnisstruktur

01 $ ls -R flux
02 flux
03  |- ...
04  |-js
05  |  |- action:
06  |  |  |- ReportAction.js
07  |  |- components
08  |  |  |- Report.react.js
09  |  |  |- ReportOptions.react.js
10  |  |- dispatcher
11  |  |  |- AppDispatcher.js
12  |  |- stores
13  |  |  |- AppStore.js
Abbildung 5: So sieht die Arbeitsteilung dank des Flux-Modells aus.

Formt der Entwickler die Beispielanwendung mit Hilfe von Flux um, würde Flux den Code, der den Standort mittels Geolocation-API ermittelt, in eine Funktion auslagern und im Unterordner »action« speichern. Die oben beschriebenen Komponenten würde Flux unter »components« ablegen.

Der Dispatcher ist eine Erfindung von Flux. Er lauscht auf Aktionen und gibt deren Resultate an alle Stores weiter, die sich beim Dispatcher abgemeldet haben. Die Stores landen im gleichnamigen Verzeichnis »stores« . Sie enthalten das Datenmodell inklusive seiner Geschäftslogik. Im Fall der Beispielanwendung betrifft das die Erdbebendaten und die Sortierfunktion »geoSort()« . Ändert sich das Datenmodell, aktualisiert der Store die Browseransicht, indem er wie gehabt die Methode »setState()« aufruft.

Listing 8 zeigt schließlich einen einfachen Store, wobei »counter« in Zeile 2 das Datenmodell speichert – einen Zähler. Die Methode »callback()« (Zeile 3) inkrementiert das Modell und aktualisiert die View in React über die Rückruffunktion »reactCallback()« . Die Zeilen 8 bis 10 registrieren die Methode »callback()« beim Dispatcher aus Listing 9. Dieser fügt »callback()« in den Zeilen 3 bis 5 seiner Liste mit Rückruffunktionen hinzu. Adressiert nun eine Aktion die Methode »raise()« (Zeile 6), so leitet diese das Ereignis an alle registrierten Rückruffunktionen weiter.

Listing 8

Beispiel-Store

01 var Store = {
02     counter: 0,
03     callback: function(event) {
04         reactCallback(++this.counter);
05     }
06 };
07
08 Dispatcher.register(function(event) {
09     Store.callback(event);
10 });

Listing 9

Dispatcher

01 var Dispatcher = {
02     cbks: [],
03     register: function(cbk) {
04         this.cbks.push(cbk);
05     },
06     raise: function(event) {
07         var i = this.cbks.length;
08         while(i--) {
09             this.cbks[i](event);
10         }
11     }
12 };

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 5 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Developer Tools für React fertig

    Laut Ben Alpert aus dem React-Projekt haben die neuen Entwicklertools für React den Betastatus verlassen. Sie helfen beim Einsatz von Facebooks Javascript-Bibliothek, mit deren Hilfe sich grafische Oberflächen entwickeln lassen.

  • Russland zählt ReactOS zur offiziellen Windows-Alternative

    Russland versucht sich in Sachen Software von der Abhängigkeit von Herstellern und Lizenzen zu befreien. Die dafür eingesetzte Initiative des Ministeriums für Telekommunikation hat nun den freien Windows-Nachbau ReactOS als Alternative für Windows in ihre Empfehlungsliste aufgenommen.

  • Tragendes Rahmenwerk

    Wer Web- oder SaaS-Anwendungen anbieten will, bekommt es mit Kodierungsrelikten der HTML- und Skriptsprachen-Frühzeit zu tun. Abhilfe schaffen moderne Frameworks wie Prado, die viele Elemente und Templates bereits als fertige Bausteine bereitstellen. Doch deren Struktur will zuerst verstanden sein.

  • React 15: Schneller und vollständiger

    Signifikante Verbesserungen im Umgang mit dem DOM und vollen SVG-Support versprechen die Macher für Version 15 von React.

  • Flux heißt jetzt Vizceral und ist Open Source

    Im Oktober 2015 stellte Netflix im hauseigenen Blog die Software Flux vor. Nun veröffentlicht die Firma die Software unter neuem Namen und unter einer Open-Source-Lizenz als Vizceral.

comments powered by Disqus

Ausgabe 10/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.