Drehte sich der erste Artikel zum Electron-Framework um das Installieren und plattformübergreifende Paketieren einer simplen App, nimmt die Fortsetzung nun ein komplexeres Beispiel in Angriff. OS Monitor bringt unter Windows, OS X und Linux Systemwerte auf den Schirm und warnt den User in kritischen Fällen.
Mit dem Electron-Framework [1] lassen sich plattformunabhängige Desktop-Apps mit HTML, CSS und Javascript fast wie für den Browser programmieren. Nach dem ersten Teil [2] prüft der zweite nun die Praxistauglichkeit von Electron anhand einer komplexeren Beispiel-App namens OS Monitor (Abbildung 1). Die bringt einen Ressourcen- und Task-Monitor im Stil des Linux-Standardtools Top mit (Abbildung 2), funktioniert als Desktop-Lösung aber auch auf Windows- und OS-X-Systemen.

Abbildung 1: OS Monitor zeigt auch unter Ubuntu 16.04 Werte für Speicher- und CPU-Auslastung an, wobei es die Liste laufender Prozesse nach der Letzteren sortiert.

Abbildung 2: Als Vorbild für OS Monitor stand das Linux-Standardtool »top« Pate, das die Abbildung in Aktion zeigt.
Die dafür benötigten Node-Pakete listet die Datei »package.json« aus Listing 1 unter dem Schlüsselwort »dependencies« auf. Die darüber geladenen Pakete verfolgen verschiedene Ziele. So generiert »ps-list« eine Liste laufender Prozesse, während sich Jquery [3] zusammen mit Popper.js [4] und Bootstrap [5] um die responsiven Webseiten kümmert. Die sollen auch auf Mobilgeräten reibungslos funktionieren. In Zeile 7 sorgt »electron-settings« dafür, Anwendungsdaten persistent zu machen. Die Einträge »electron-compile« und »electron-squirrel-startup« aus den letzten beiden Zeilen benötigt das Electron-Framework aus internen Gründen.
Listing 1
package.json
01 [...]
02 "dependencies": {
03 "ps-list": "^4.0.0",
04 "jquery": "^1.9.1",
05 "popper.js": "^1.12.3",
06 "bootstrap": "^4.0.0-beta.2",
07 "electron-settings": "^3.0.0",
08 "electron-compile": "^6.4.2",
09 "electron-squirrel-startup": "^1.0.0"
10 }
11 [...]
Hat der Entwickler die Datei gespeichert, installiert der Aufruf von Nodes Paketmanager Yarn [6] die beschriebenen Module aus dem NPM-Paket-Repository [7] unter dem Projektverzeichnis im Ordner »node_modules«.
Am Steuer
Die Steuerung der App übernimmt der Hauptprozess aus Listing 2. Er steckt in der Datei »index.js« im »src«-Verzeichnis. Der Code läuft in der Programmierumgebung Node und bildet den Einstiegspunkt der App. Listing 2 importiert in Zeile 1 die Objekte »app« und »Menu« sowie die Klasse »BrowserWindow« aus dem Paket »electron«. Zeile 2 erzeugt dann mit »settings« ein Objekt, das die Anwendungsdaten aufnimmt. Das geschieht über einen Aufruf der Funktion »require()«, wobei »electron-settings« als Aufrufparameter dient. Die Funktion bindet die zurückgegebene Referenz an die Konstante mit dem Namen »settings«.
Listing 2
src/index.js
01 import { app, BrowserWindow, Menu } from 'electron';
02 const settings = require('electron-settings');
03
04 let monitorWindow = null,
05 settingWindow = null;
06
07 if (require('electron-squirrel-startup')) {
08 app.quit();
09 }
10
11 const creWindow = (win, path, opts={}) => {
12 if (win === null) {
13 win = new BrowserWindow(opts);
14 //win.webContents.openDevTools();
15 win.loadURL(`file://${__dirname}/${path}`);
16 win.on('closed', () => { win = null });
17 }
18 }
19
20 app.on('ready', () => {
21 if (!settings.has('delay')) {
22 settings.setAll({
23 critMem: 100, critCPU: 100, delay: 3000
24 });
25 }
26 Menu.setApplicationMenu(Menu.buildFromTemplate ([
27 { label: 'Menu',
28 submenu: [
29 {label: 'Settings', click () {creWindow(settingWindow, 'setting/setting.html', {width: 400, height: 250, alwaysOnTop: true})}},
30 {type: 'separator'},
31 {role: 'close'}
32 ]
33 }
34 ]));
35 creWindow(monitorWindow, 'monitor/monitor.html');
36 });
37
38 app.on('window-all-closed', () => {
39 if (process.platform !== 'darwin') {
40 app.quit();
41 }
42 });
43
44 app.on('activate', () => {
45 creWindow(monitorWindow, 'monitor/monitor.html');
46 });
Die mit dem »null«-Objekt initialisierten lokalen Variablen »monitorWindow« und »settingWindow« speichern im späteren Programmverlauf eine Referenz auf das Haupt- und auf das Konfigurationsfenster der Anwendung. Ließe der Entwickler die Zeilen 7 bis 9 weg, würden nach dem Installieren der App unter Windows mit Hilfe des Installations- und Update-Frameworks Squirrel [8] die Schnellstartverknüpfung auf dem Desktop sowie die erforderlichen Berechtigungen in den Datenschutzeinstellungen fehlen.
Die Funktion »creWindow()« ruft ein App-Fenster im Singelton-Stil auf den Plan, was bedeutet, dass von ihm genau ein Objekt existiert. Die Zeile 11 stattet die Funktion zudem mit den drei Aufrufparametern »win«, »path« und »opts« aus. Je nach Anwendung übernimmt »win« entweder die Referenz aus der Variablen »monitorWindow« oder die aus »settingWindow«. Hält »win« bereits eine Referenz auf ein Fensterobjekt, verwehrt Zeile 12, dass der Codeblock in den Zeilen 13 bis 16 ein zweites erzeugt.
Ist das hingegen nicht der Fall, generiert der Codeblock ein neues Fenster, indem er den Konstruktor der Klasse »BrowserWindow« aufruft. Anschließend speichert er die zurückgegebene Referenz mit Hilfe der lokalen Variablen »win« in »monitorWindow« oder »settingWindow« ab. Sein Konfigurationsobjekt übernimmt der Konstruktoraufruf dann aus dem Aufrufparameter »opts«. Ist die Methode »openDevTools()« in Zeile 14 nicht auskommentiert, erscheinen im soeben erzeugten Fenster zudem die Debugging-Tools.
Die Methode »loadUrl()« lädt in Zeile 15 analog zum Browser ein HTML-Dokument als Startpunkt der Anwendung. Dabei verwendet sie einen absoluten Pfad, der entsteht, wenn sie die Werte der Variablen »__dirname« und »path« expandiert. Die Spezialvariable »__dirname« speichert den absoluten Pfad zum Verzeichnis, in dem »index.js« auf seinen Einsatz wartet, während »path« relativ dazu den Pfad der zu ladenden HTML-Seite verwahrt.
Zeile 16 registriert eine Callback-Funktion (»() => { win = null }«), die auf das Schließen des Fensters über das Ereignis »closed« reagiert. Dafür kommt die Methode »on()« zum Einsatz. Sobald sich das Fenster schließt, re-initialisiert der Ausdruck »win = null « die Variablen »monitorWindow« oder »settingWindow« wieder mit dem »null«-Objekt. Indirekt geschieht dies über »win«.
Die Anwendung startet, wenn das »app«-Objekt mit der Callback-Funktion ab Zeile 20 reagiert. Beim ersten Start speichert die Methode »setAll()« das übergebene Objekt mit Defaultwerten dauerhaft als App-Einstellungen (Zeile 22). Das geschieht über das »settings«-Objekt aus dem Paket »electron-settings«.
Die Methode »setApplicationMenu()« aktiviert ab Zeile 26 das ihr übergebene Menü-Objekt. Dies hat zuvor die Methode »buildFromTemplate()« aus dem in den Zeilen 27 bis 33 übergebenen Objekt erzeugt. Das Menü funktioniert in der gesamten App, Abbildung 3 zeigt es geöffnet unter Windows 10. Es trägt den Bezeichner »Menu«. Das zugehörige Untermenü definieren die Zeilen 28 bis 32 ab dem Schlüssel »submenu«.

Abbildung 3: Die Menüs funktionieren anwendungsweit und betriebssystemübergreifend – in der Abbildung unter Windows.
Ein Klick auf den ersten Eintrag der Liste mit dem Bezeichner »Settings« öffnet das Konfigurationsfenster der App (Abbildung 4). Technisch speichert das Skript für das »click()«-Event einen entsprechend parametrisierten Aufruf der Funktion »creWindow()« (Zeile 29). Der Aufruf übergibt in diesem Zuge die Referenz »settingWindow«, den relativen Pfad zur HTML-Seite aus Listing 7, ein Objekt mit Fensterabmessungen sowie die Direktive »alwaysOnTop«. Der Eintrag »{type: ‘separator’}« erzeugt den dünnen Trennstrich zwischen »Settings« und »Close« (Abbildung 3).
Die Zeile 31 generiert den Eintrag »Close« zum Schließen des Fensters. Dazu greift sie über den Schlüssel »role« auf die vordefinierte Referenz »close« zu. Eine Abkürzung für Anwender über das Tastenkürzel [Strg]+[W] liefert das Skript übrigens frei Haus mit.
Zeile 35 verwendet erneut »creWindow()«, um das Hauptfenster der Anwendung aus Abbildung 1 zu öffnen. Der Code ab Zeile 38 beendet die App standardmäßig über die Methode »quit()« des Anwendungsobjekts »app« nach dem Schließen aller Fenster – mit einer Ausnahme für Apples OS Darwin. Dessen Besonderheiten widmet sich auch die letzte Callback-Funktion im Listing: Ein Klick auf das Dock-Icon erzeugt das Hauptfenster bei Bedarf neu.
Eindringlinge
Eine Instanz der Klasse »BrowserWindow« aus Listing 2 läuft analog zu einem Browser-Tab unter Chromium in einem eigenen Prozess, dem Renderprozess. Anders als Chromium unterstützt eine »BrowserWindow«-Instanz Node statt Javascript, die Konsequenz dieses Umstands demonstriert Listing 3.
Listing 3
src/renderer.js
01 # Remote-Umgebung für den Renderprozess importieren
02 window.$ = window.jQuery = require('jquery');
03 window.Popper = require('popper.js');
04 const bootstrap = require('bootstrap');
05 exports.settings = require('electron').remote.require('electron-settings');
06 exports.remote = require('electron').remote;
Zeile 2 bindet die Javascript-Bibliothek Jquery über die Funktion »require()« ein. Der Renderprozess bindet das Jquery-Objekt in Zeile 2 explizit an die globalen Variablen »jQuery« und »$«, das übliche »<script>«-Tag aus HTML kommt hierfür nicht in Frage.
Zeile 3 bindet die Javascript-Bibliothek Popper.js, Zeile 4 holt Bootstrap ins Boot. Letzteres nistet sich über die beiden globalen Variablen bei seinem Gastgeber Jquery ein, um zusammen mit Popper.js und mit eigenen CSS-Formatangaben aus simplem HTML eine responsive Webseite zu machen.
Zeile 5 importiert das Settings-Objekt aus dem Main Process aus Listing 2. Würde »require(‘electron’).remote« fehlen, erzeugte die Zeile ein leeres Settings-Objekt im Sichtbarkeitsbereich des Fensters. Der Code in Listing 3 bindet die zurückgegebene Referenz aber auf der linken Seite des Gleichheitszeichen an das Exportobjekt des Pakets.
Dieser Schritt wiederholt sich in der folgenden Zeile 6 mit der Referenz auf den Main Process unter dem Bezeichner »remote«. Indem der Entwickler über die Referenz »remote« Objekte aus dem Main Process verwendet, vermeidet er es in der Regel, die Anwendungsdaten mit dem Renderprozess über Interprozesskommunikation (IPC) auszutauschen.
Da Main- und Renderprozess aber jeweils in eigenen Prozessen laufen, sollte er nach Störenfrieden wie Data Races und Deadlocks Ausschau halten. Wer auf den Einsatz von Node verzichten möchte, generiert Fenster übrigens mit
new BrowserWindow({webPreferences: {nodeIntegration: false}})
Im nächsten Schritt gilt es, das Hauptfenster zu erzeugen.
So sieht’s aus
Das HTML-Dokument in Listing 4 beschreibt die Seitenstruktur des Hauptfensters der App. Zugleich erlaubt es, die Anwendungsdaten zu generieren, indem es ein Node-Programm einbindet. Das »meta«-Tag im Kopf des HTML-Dokuments sorgt dafür, den Browser-Viewport optimal zu konfigurieren, indem es bewährte Einstellungen aus dem Webdesign übernimmt. Zeile 7 gibt im »href«-Attribut den relativen Pfad zur Datei »bootstrap.css« an, die bei den installierten Node-Paketen wartet.
Listing 4
src/monitor/monitor.html
01 <!doctype html> 02 <html lang="en"> 03 <head> 04 <title>OS Monitor</title> 05 <meta charset="utf-8"> 06 <meta name="viewport" content= "width=device-width, initial-scale=1, shrink-to-fit=no"> 07 <link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.css"> 08 </head> 09 <body> 10 <div class="card text-center"> 11 <div class="card-body"> 12 <table class="table"> 13 <thead> 14 <tr> 15 <th>Total Mem (kiB)</th> 16 <th>Mem Usage (%)</th> 17 <th>CPU Usage (%)<th> 18 </tr> 19 </thead> 20 <tbody> 21 <tr> 22 <td id="totMem"></td> 23 <td id="relMem"></td> 24 <td id="totCpu"></td> 25 </tr> 26 </tbody> 27 <thead> 28 <tr> 29 <th id="pid">PID</th> 30 <th id="name">Name</th> 31 <th id="cpu" class="desc">CPU</th> 32 </tr> 33 </thead> 34 <tbody id="plist"></tbody> 35 </table> 36 </div> 37 </div> 38 </body> 39 <script src="./monitor.js"></script> 40 </html>
Den Body des HTML-Dokuments dominiert eine Tabelle. Sie präsentiert die Anwendungsdaten in drei Spalten. Der erste Tabellenkopf (Zeilen 15 bis 17) legt Überschriften für die Spalten zur Speichergröße sowie zur prozentualen Speicher- und CPU-Auslastung fest. Die korrespondierenden Werte folgen in den Zellen, die die Zeilen 22 bis 24 vorgeben. In die leeren »<td>«-Elemente schreibt Jquery periodisch die aktuell gemessenen Werte, wobei es die Zellen anhand ihrer »id«-Elemente identifiziert.
Der nächste Tabellenkopf (Zeilen 27 bis 33) steuert weitere Überschriften für die Prozessliste bei. Diese kippt Jquery periodisch unter der »id=”plist”« in den Tabellenkörper (Zeile 34). In Zeile 39 lädt das »<script>«-Element die Anwendungsdaten aus »monitor.js«.
Bootstrap formatiert das Ganze mit den Klassenattributen ab Zeile 10. Die CSS-Klasse »card« sorgt für einen Karteikarten-Look, »text-center« zentriert Text, während »card-body« den Textbereich der Karteikarte frisiert. Die Klasse »table« holt die flexiblen Formateigenschaften für Tabellen aus Bootstrap, damit sich die Tabelle dynamisch anderen Bildschirmgrößen anpasst.
Leseschwäche
Listing 5 zeigt den Code aus der eben erwähnten Datei »monitor.js«. Die erste Zeile importiert das Settings-Objekt, das Listing 3 definiert hat, die zweite greift auf die Hilfsfunktionen »cpuu«, »notify« und »transform« zu. Die stammen aus der Datei »monitor.func.js« (Listing 6), der sich der Artikel weiter unten zuwendet.
Listing 5
src/monitor/monitor.js
01 const {settings} = require('../renderer.js');
02 const {cpuu, notify, transform} = require('./monitor-func')
03 const psList = require('ps-list');
04 const os = require('os')
05 let critMem = settings.get('critMem'),
06 critCPU = settings.get('critCPU');
07 $(document).ready(() => {
08 setInterval(() => {
09 let totCpu = cpuu(os.cpus()),
10 relMem = Math.round((1 - os.freemem()/os.totalmem())*100);
11 $('#relMem').text(relMem);
12 $('#totMem').text(Math.round(os.totalmem()/1024));
13 $('#totCpu').text(totCpu);
14 notify('Memory usage', relMem, critMem);
15 notify('CPU usage', totCpu, critCPU);
16 }, settings.get('delay')
17 );
18 const pslist = () => { psList().then(pl => {
19 $('#plist').empty();
20 transform(pl);
21 setTimeout(pslist, settings.get('delay'));
22 })};
23 setTimeout(pslist, settings.get('delay'));
24 settings.watch('critMem', n => critMem = n);
25 settings.watch('critCPU', n => critCPU = n);
26 });
In Zeile 3 importiert der Code ein Objekt zum Lesen der Prozessliste aus dem Paket »ps-list«. Zeile 4 legt ein Objekt an, um die Meta-Angaben des Betriebssystem über das Paket »os« lesen zu können. Es folgt ein Aufruf der Methode »get()«. Er initialisiert die Variablen »critMem« und »critCPU« mit Werten aus dem Settings-Objekt, indem er geeignete Bezeichner übergibt.
Mit der Callback-Funktion ab Zeile 7 reagiert der Browser auf das vollständige Laden des DOM-Baums. Dazu bindet Jquery die Callback-Funktion über die Methode »ready()« an das Dokument-Objekt »document«. Die Auswahl übernimmt – wie in Jquery üblich – die so genannte Dollar-Funktion (Signatur: »obj $(obj selector)«). Diese übergibt wahlweise ein Javascript-Objekt oder einen CSS-Selektor als Zeichenkette.
Innerhalb der Callback-Funktion setzt die Funktion »setInterval()« eine Periode für die Datenaktualisierung. Die zugehörige Wiederholrate erhält sie über den Ausdruck »settings.get(‘delay’)« aus dem Settings-Objekt und übergibt sie im zweiten Parameter in Zeile 16.
Die Hilfsfunktion »cpuu()« berechnet in Zeile 9 die durchschnittliche Auslastung der CPUs. Die Angaben dazu holt sie in Listenform über den Aufruf von »cpus()« aus dem Objekt »os«. Die relative Speicherauslastung berechnet die Zeile 10. Sie versorgt sich mit Messdaten aus dem »os«-Objekt und legt diese in der Variablen »relMem« ab. Die folgenden drei Zeilen platzieren dann die berechneten Werte über die jeweiligen ID-Attribute im HTML-Dokument, also in der grafischen Oberfläche der App.

Abbildung 5: Die Benachrichtigungen von OS Monitor funktionieren plattformübergreifend. Sie erscheinen unter Windows 10 …
Die Callback-Funktion schließt mit zwei Aufrufen der Funktion »notify()«. Die übernimmt in beiden Fällen im ersten Parameter eine Meldung, im zweiten einen tatsächlichen Wert und im dritten einen Schwellenwert, der den Alarm auslöst. Abbildung 5 zeigt die Meldungen aus OS Monitor unter Windows 10, Abbildung 6 unter Ubuntu.
Dass die Funktion »psList()« ein Event-bezogenes Konstrukt ist, liegt an Windows 10. Bei dem Betriebssystem verursacht das Lesen der Prozessliste einen Task-Rückstau (Abbildung 7). Aus diesem Grund funktioniert ein statisch getakteter Aufrufmechanismus nicht.

Abbildung 7: Ein ungeklärter Rückstau müllt die Task-Liste unter Win32 zu. In diesem Fall muss der Entwickler nacharbeiten.
Der Rückstau bildet sich durch überlange Wartezeiten, die beim Ausführen des Kommandos »tasklist« im gleichnamigen Node-Paket entstehen – der zugehörige Aufruf lautet »pify(childProcess.execFile)(‘tasklist’, args)«. Tatsächlich widmet sich immer nur eine Task dem Systemaufruf »tasklist«. Der Grund: Der rekursive Aufruf der Funktion »pslist()« erfolgt zu spät, nämlich nach dem erfolgreichen Lesen der Prozessliste im Callback zum Promise-Objekt in Zeile 21.
Vor dem Selbstaufruf leert Zeile 19 mit der Jquery-Methode »empty()« den Tabellenkörper der HTML-Seite »monitor.html«. Dann füllt die Methode sie direkt mit den Einträgen der Projektliste (Variable »pl«) und unter Einsatz von »transform()« wieder zeilenweise.
Hilfe naht
Listing 5 zieht die Hilfsfunktionen aber nicht frei aus der Luft, sondern zapft sie über den »require()«-Aufruf in seiner zweiten Zeile aus Listing 6 an. Das berechnet in den ersten Zeilen einen relativen Mittelwert zur CPU-Auslastung. Dazu wendet es die Funktion »workload()« in der zweiten Zeile wiederholt an, wobei diese die jeweilige Leerlaufzeit (»idle«) berücksichtigt.
Listing 6
src/monitor/monitor.func.js
01 # Lambda-Funktionen ((a) => return-expression) generieren in eine Schar von Helfern
02 const workload = e => Math.round((1 - e.idle/(e.idle + e.irq + e.sys + e.user + e.nice))*100);
03 const cpuu = e => e.map(c => workload(c.times)).reduce((r, a) => r + a, 0)/e.length;
04 const normalize = e => { return {name: coal(e.name, ""), pid: coal(e.pid, ""), cpu: coal(e.cpu, 0)}};
05 const coal = (val, def) => val === undefined?def:val;
06 const notification = (body) => new Notification('Warning', {body: body});
07 const notify = (type, value, limit) => (value > limit)?notification(type+' >'+limit.toString()+'%'):null;
08 const sort = (a, b, desc) => (parseFloat(a) > parseFloat(b))?1:0;
09 const sortCrit = (a, b) => sort(a.cpu, b.cpu);
10 const tr = e => '<tr><td>'+e.pid+'</td><td>'+e.name+'</td><td>'+e.cpu+'</td></tr>';
11 const transform = e => e.map(e => normalize(e)).sort(sortCrit).forEach(e => $('#plist').prepend(tr(e)));
12 module.exports = { transform, notify, cpuu }
Basierend darauf erzeugt die Listenmethode »map()« (Zeile 3) erst einen Durchschnittswert pro CPU als Prozentwert der Gesamtnutzung. Die daraus resultierende Liste an Zahlen summiert »reduce()« in bekannter Manier. Nach einer Normierung und Rundung enthält »cpuu()« den relativen Mittelwert zur CPU-Auslastung. Liegt der tatsächliche Wert über einem Schwellenwert, informiert »notify()« das Betriebssystem, indem es die Funktion »notification()« in Zeile 6 aufruft.
Auch für die Funktion »transform()« definiert Listing 6 einige Bedingungen. Sie verwendet »map()«, um jeden Eintrag aus der Prozessliste über den Aufruf von »e => normalize(e)« zu vervollständigen. Dazu füllt »normalize()« (Zeile 4 von Listing 6) fehlende Werte mit Hilfe der Coalesce-Implementierung »coal()« (Zeile 5) mit Defaultwerten auf.
Zeile 9 terminiert die Sortierung der vervollständigten Liste, wenn der paarweise Aufruf der Funktion »sortCrit()« stets einen Wert größer gleich null zurückgibt. Die sortierte Liste gibt in Zeile 11 die Methode »forEach()« Element-weise im Tabellenkörper der Datei »monitoring.html« (Listing 4) aus. Dazu nutzt sie über den Aufruf »e => $(‘#plist’).prepend(tr(e))« die Methode »prepend()«. Zuvor wandelt die Funktion »tr()« (Zeile 10 von Listing 6) einen Eintrag aus der Prozessliste in eine Tabellenzeile um.
Spurensuche
Listing 7 zeigt die HTML-Seite des Konfigurationsfensters, das im Körper der Karteikarte (Zeilen 11 bis 33) ein Formular speichert. Das bietet Eingabemöglichkeiten für die Parameter »critMem«, »critCPU« und »delay« in Form eines Textfelds vom Typ »number« an. Dabei legen »critMem« und »critCPU« prozentuale Schwellenwerte für Speicher- und CPU-Auslastung fest, deren Überschreiten eine Systembenachrichtigung generiert.
Listing 7
src/setting/setting.html
01 <!doctype html> 02 <html lang="en"> 03 <head> 04 <title>Settings</title> 05 <meta charset="utf-8"> 06 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 07 <link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.css"> 08 </head> 09 <body> 10 <div class="card text-center"> 11 <div class="card-body"> 12 <form id="setting" class="form"> 13 <div class="form-group row"> 14 <label for="critCPU" class="col-8 col-form-label">Notify CPU Usage ></label> 15 <div class="col-4"> 16 <input id="critCPU" type="number" class="form-control" > 17 </div> 18 </div> 19 <div class="form-group row"> 20 <label for="critMem" class="col-8 col-form-label">Notify Memory Usage ></label> 21 <div class="col-4"> 22 <input id="critMem" type="number" class="form-control"> 23 </div> 24 </div> 25 <div class="form-group row"> 26 <label for="delay" class="col-8 col-form-label">Delay (ms)</label> 27 <div class="col-4"> 28 <input id="delay" type="number" class="form-control"> 29 </div> 30 </div> 31 <button type="submit" class="btn btn-primary">Save</button> 32 </form> 33 </div> 34 </div> 35 </body> 36 <script src="./setting.js"></script> 37 </html>
Der Eintrag »delay« enthält die Aktualisierungsrate für das Hauptfenster aus Abbildung 1, ein Wert in Millisekunden. Die Bootstrap-Klasse »form« sorgt in Zeile 12 für globale CSS-Formatangaben für Formulare. Die Klasse »form-group« markiert dagegen jeweils einen logischen Formularblock aus Bezeichner und Textfeld, den die Klasse »row« zeilenartig formatiert. »col-lable-8« lässt den jeweiligen Bezeichner des Formularelements auf Basis von Bootstraps zwölfteiligem Grid-Modell [9] acht Spalten überspannen, »col-4« überspannt die verbleibenden vier Spalten als Raum zur Darstellung des Textfelds.
Abfangjäger
Listing 8 fängt die Klicks beim Versenden des Formulars aus Listing 7 ab und wertet sie aus. Analog zu Listing 5 importiert Zeile 2 die Referenz »remote« aus Zeile 5 von Listing 3. Das Programm lädt den DOM-Baum und speichert in Zeile 4 die Bezeichner »cirtMem«, »critCPU« und »delay« im Feld »fields«.
Listing 8
src/setting/setting.js
01 # Das Synchronisieren von Formular- und Settings-Objekt erinnert an die Binding-Mechanismen von Angular und React
02 const {settings, remote} = require('../renderer.js');
03 $(document).ready(() => {
04 const fields = ['critMem', 'critCPU', 'delay'];
05 const _d = n => parseFloat($('#'+n).val());
06 const _s = n => parseFloat(settings.get(n));
07 fields.map(n => $('#'+n).val(_s(n)));
08 $('#setting').submit((e) => {
09 e.preventDefault();
10 if (_d('delay') != _s('delay')) {
11 alert('Change of delay requires a restart!');
12 }
13 fields.map(n => settings.set(n, _d(n)));
14 remote.getCurrentWindow().close();
15 });
16 });
Die Funktion »_d« merkt sich die Werte aus den Textfeldern, »_s« jene aus dem »settings«-Objekt. In beiden Fällen konvertiert »parseFloat()« den Rückgabewert in den Typ »Number«.
Zeile 7 füllt für jeden Bezeichner der Liste »fields« die Textfelder aus Listing 7, indem sie die Funktion »n => $(‘#’+n).val(_s(n))« anwendet. Dabei verbindet der »+«-Operator den Feldbezeichner und den »#«-Operator zu einem CSS-»id«-Selektor. Klickt der Nutzer auf »Save« (Zeile 31 in Listing 7), löst dies die Funktion in den Zeilen 8 bis 15 aus, die Textfelder im »settings«-Objekt speichert. Den Callback weist das Listing dem Formular dabei über den ID-Selektor »#setting« zu. Beim Klick auf den »submit«-Button übernimmt die Funktion standardmäßig das Event-Objekt »e«.
In Zeile 9 verhindert »prevent.Default()« zunächst, dass die App das Formular per HTTP versendet. Die nächste Zeile erinnert daran, dass der neue Wert für »delay« erst nach dem Neustart der App gültig wird. Dazu vergleicht das Listing den Wert aus dem Formular-Feld mit dem aus dem »settings«-Objekt und reagiert gegebenenfalls mit einer Alert-Box.
Zeile 13 übermittelt in Umkehrung zu Zeile 7 die Werte aus dem Formular an das »settings«-Objekt. Die letzte Zeile bedient sich der Referenz »remote«, um das aktuelle Fenster über »remote.getCurrentWindow()« auszuwählen und mit Hilfe der Methode »close()« zu schließen.
Reifeprozess
OS Monitor zeigt einen überschaubar komplexen Use Case für Electron auf Basis von HTML, CSS, Bootstrap, Node und Jquery. Der Einsatz der Lambda-Funktionen in Listing 6 ergibt durchaus Sinn: Er hilft Ausdrücke kurz zu halten und sinnvoll zu sortieren.
Wer sich mehr Funktionalität von seinen Apps wünscht, sollte den Aufwand nicht scheuen und sich in Angular [10] oder React [11] einarbeiten. Ein leeres Angular-Projekt unter Electron erzeugt übrigens der Befehl »forge init osmonitor2 –template=angular2«. Das Update auf Angular 5 gelingt im Nachhinein, wenn der Entwickler in »osmonitor2/packges.json« die Liste der Angular-Pakete auf den aktuellen Stand bringt.
Wer Fehler der App im Regelbetrieb aufspüren möchte, greift zu Electrons Crashreporter [12]. Hat er die Bugs behoben, kann er über Electrons Auto-Updater [13] Service Releases ausrollen, ohne die Benutzer behelligen zu müssen.
Generell öffnet Electron aber auch Anwendern mit geringen Fertigkeiten unter HTML, CSS und Javascript die Möglichkeit, Desktop-Apps zu entwickeln. Laufen sie unter Chromium, funktionieren sie auch mit Electron. Wie das Node-Package »ps-list« zeigt, ist beim Verlassen der Browser-Sandbox aber Vorsicht geboten – letztlich ist eine fertige Electron-App nur so gut, wie die ihr zugrunde liegenden Node-Pakete.
Dank des Pack-Tools »forge«, des Crashreporter und des Auto-Updater eröffnet Electron zumindest die Möglichkeit, Fehler zu erkennen und Patches dafür Plattform-spezifisch automatisiert auszurollen – leider nur auf Desktop-Betriebssystemen. Den Sprung auf Android und I-OS, wie ihn React Native [14] anpeilt, wagt Electron nicht, was das Rennen um eine universelle Entwicklungsumgebung für Apps weiter offen hält.
Infos
- Electron: https://electron.atom.io
- Andreas Möller, “Code im Beschleuniger”: Linux-Magazin 02/18, S. 70.
- Jquery: https://jquery.com
- Popper.js: https://popper.js.org
- Bootstrap: https://getbootstrap.com
- Yarn: https://yarnpkg.com/lang/en/
- NPM Javascript-Repository: https://npmjs.org
- Squirrel: https://github.com/Squirrel/Squirrel.Windows
- Grid-System von Bootstrap: https://www.w3schools.com/bootstrap/bootstrap_grid_system.asp
- Angular: https://angular.io
- React: https://reactjs.org
- Crashreporter: https://electronjs.org/docs/api/crash-reporter
- Auto-Updater: https://electronjs.org/docs/api/auto-updater
- Reactive Native: https://facebook.github.io/react-native/








