Ein Code, keine Grenzen

Cross-Plattform-Desktop-Apps mit NW.js erstellen
Kommentare

Das Thema Cross-Plattform-Entwicklung hat aktuell zu Recht einen sehr großen Stellenwert in der IT. Anstatt viel Zeit und Geld in die Erschaffung nativer Apps auf allen mobilen, Desktop- und Browserplattformen zu investieren, konzentriert man sich sinnvollerweise auf die Entwicklung einer Codebasis, die auf sämtlichen Plattformen ausgeführt werden kann. Nichts wirklich Neues für die mobile Welt (siehe Tools wie Cordova oder PhoneGap), doch auf dem Desktop ist dieser Ansatz noch kein Standard.

Dabei sind bereits jetzt viele Frameworks und Technologien verfügbar, die diesen  logischen nächsten Schritt der Anwendungsevolution einfach gestalten. In diesem Artikel werden Sie lernen, wie man Cross-Plattform-Desktopanwendungen mithilfe von NW.js entwickelt, und welche Vorteile dieser Ansatz gegenüber der klassischen Herangehensweise bietet. Schauen wir uns die klassische Desktopanwendungsentwicklung an, so spezialisieren sich viele Unternehmen direkt oder indirekt auf eine Plattform – sei es entweder durch eine Businessentscheidung festgelegt oder schlicht und einfach der Tatsache geschuldet, dass der Lösungsanbieter oder das Softwarehaus lediglich über Entwicklungsressourcen verfügt, die Know-how für eine der drei großen Plattformen (Windows, Linux und Mac OS X) mit sich bringen.

Daraus resultiert unweigerlich, dass man – als Lösungsanbieter – sowohl den möglichen Benutzermarkt als auch den daraus resultierenden Umsatz von Beginn an schmälert. Im Jahre 2015 sollte man von vorneherein ohne Betriebssystemgrenzen im Kopf agieren. Befasst man sich allerdings etwas genauer mit der Cross-Plattform-Thematik, so kann dieses Problem mit einfachsten Mitteln gelöst werden.

Sie kennen vielleicht die ein oder andere oben genannte Problembeschreibung oder es spiegelt sich Ihr Team/Unternehmen darin wieder? Dann sollten Sie die folgenden Seiten aufmerksam lesen die aufzeigen, wie Sie aus dieser Ecke fliehen können. Die Kombination aus NW.js und den dazu passenden Frameworks sowie Tools werden Ihnen dabei behilflich sein, schnell Cross-Plattform-Anwendungen zu entwickeln.

Stellen Sie Ihre Fragen zu diesen oder anderen Themen unseren entwickler.de-Lesern oder beantworten Sie Fragen der anderen Leser.

Was ist NW.js eigentlich?

NW.js ist zunächst einmal ein Open-Source-Projekt mit tatkräftiger Unterstützung der Firma Intel – zudem kann jeder bei Bedarf dabei helfen, NW.js weiterzuentwickeln und zu verbessern. Die Hauptaufgabe von NW.js ist es, die Browser-Engine WebKit mit Node.js plattformübergreifend zu vereinen. Etwas konkretisiert sprechen wir im Folgenden von den beiden Implementierungen Chromium und Node.js auf den Plattformen Windows, Mac OS X und Linux (jeweils für 32 und 64 Bit).

Die beiden vorangegangenen Sätze muss man als gestandener Windows-Entwickler wohl erst einmal etwas verinnerlichen: Durch den Einsatz von NW.js schafft man es sehr leicht, eine Anwendung mit nur einer Codebasis auf allen wichtigen Plattformen bereitzustellen.

NW.js stellt Entwicklern hierbei ein überschauliches JavaScript-API zur Verfügung, um alltägliche Anwendungsaufgaben zu realisieren. Sämtliche Interaktionen mit dem Betriebssystem werden durch das Hinzufügen und Verwenden von Node.js-Modulen abstrahiert und laufen damit ebenfalls automatisch auf allen Plattformen. Sicherlich denkt jetzt der eine oder andere Leser, dass die Komplexität hier im Build-Prozess liegt und man als Entwickler ein hohes Maß an Zeit investieren müsste. Doch dem ist nicht so: Eine der Hauptanforderungen, die sich die Entwickler von NW.js selbst auferlegt haben, ist es, den Build- und Distributionsprozess einfach und schnell zu halten. Mehr hierzu später im Artikel.

NW.js Building Blocks

Was muss ich als Entwickler beherrschen, um NW.js-Anwendungen entwickeln zu können? Die Liste ist kurz, daher auf ans Werk:

  • HTML5
  • CSS3
  • JavaScript
  • Node.js und dessen Package-Manager npm

Mehr brauchen Sie nicht, um loszulegen. Sicherlich ist es hilfreich, gerade mit dem Debuggen von Webanwendungen Erfahrung zu haben; investieren Sie daher Zeit und lernen Sie die Developer-Tools Ihres Browsers kennen.

In diesem Artikel werden Sie sehen, wie eine kleine NW.js-Anwendung entsteht. Um nicht zu viel Zeit mit alltäglichen Tasks in der Webentwicklung zu verschwenden, kommen zusätzlich zu den oben genannten Sprachen noch die folgenden Frameworks/Tools zum Einsatz:

Die erste NW.js-Anwendung

Als exemplarische App werden wir im weiteren Verlauf des Artikels eine kleine To-do-App erstellen. Hier die simplen Use Cases der Anwendung „nwTaskify“:

  • Neue Aufgabe anlegen
  • Alle Aufgaben anzeigen
  • Eine Aufgabe erledigen
  • Eine Aufgabe löschen
  • Aufgaben als Datei speichern
  • Aufgaben aus Datei laden

Das Entwicklungssystem

Einige der Aktionen, die nachfolgend im Artikel beschrieben sind, müssen in einer Konsole ausgeführt werden. Die jeweiligen Befehle können – abhängig von Ihrem Entwicklungssystem und des gewählten Terminals – etwas variieren. Alle Kommandos in diesem Artikel lassen sich ohne Probleme auf einem Mac-OS-X- oder Linux-System ausführen. Als Terminal dient hier eine Standard-Bash-Terminalinstanz. Um den Quellcode der Anwendung nwTasikfy zu schreiben, können Sie den Editor oder die Entwicklungsumgebung (IDE) Ihrer Wahl verwenden.

Let’s go, NW.js

NW.js-Anwendungen brauchen zunächst ein so genanntes Manifest, das Rahmeninformationen der Anwendung bereitstellt und deren Fähigkeiten genau beschreibt. Auch an dieser Stelle nichts Neues. Hier wird der von Node.js bekannte Standard, das package.json-File verwendet. Starten wir an dieser Stelle mit dem neuen Projekt (Listing 1).

 
# Projektverzeichnis anlegen
~>mkdir nwtaskify
~>cd nwtaskify
# package.json generieren lassen (NPM Wizard)   
~/nwtaskify>npm init 
# package.json im Editor anpassen  
~/nwtaskify>sublime package.json

NW.js stellt nicht viele Anforderungen an die minimalste Form der package.json-Datei. Sie müssen mindestens die Properties name, version und main angeben. Die name-Eigenschaft dient als Identifier Ihrer Anwendung (nicht der Display-Name) und die main-Property teilt NW.js mit, welche HTML-Datei nach dem Start der Anwendung geladen werden soll. Die version-Eigenschaft wird genutzt, um die Version Ihrer NW.js-App zu bestimmen. Editieren Sie das package.json-File und geben Sie die folgenden Werte an

{
  // ...
  "name": "nwTaskify",
  "main": "./app/index.html",
  "version": "0.1.0"
  // ...
}

Erstellen Sie nun die oben angegebene index.html-Datei im Ordner app im nwTaskify-Verzeichnis.

Paketabhängigkeiten installieren

Um korrekt mit clientseitigen Abhängigkeiten umzugehen, wird an dieser Stelle der Paketmanager Bower verwendet. Sollten Sie sich noch nicht mit Bower beschäftigt haben, investieren Sie an dieser Stelle gerne ein paar Minuten und lernen Sie Bower und sein Command Line Interface (CLI) näher kennen.

# use & finish the bower wizard
~/nwtaskify>bower init
# install dependencies
~/nwtaskify>bower install angularjs –save
~/nwtaskify>bower install bootstrap –save

Sowohl AngularJS als auch Twitter-Bootstrap sind nach der erfolgreichen Ausführung der Kommandos im Unterordner bower_components installiert und können von dort verwendet werden. Der Code in Listing 2 zeigt die Datei app/index.html in der ersten Ausbaustufe.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="styles/vendor/bootstrap.min.css">
    <link rel="stylesheet" href="styles/app.css">
  </head>
  <body ng-app="nwTaskify" ng-controller="taskController as tasks">
    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">nwTaskify</a>
        </div>
        <ul class="nav navbar-nav navbar-right">
          <!-- IMPORT / EXPORT -->
          <li role="separator" class="divider"></li>
          <li><a href="#" ng-click="tasks.showRepo()">SourceCode</a></li>
        </ul>
      </div>
    </nav>
    <div class="container-fluid">
      <div class="panel panel-info">
        <div class="panel-heading">
          <h5>Add a new Task</h5>
        </div>
        <div class="panel-body">
          <div class="row">
            <div class="col-sm-10">
              <input type='text' ng-model='tasks.newTask' placeholder="What's up next?" class='form-control' />
            </div>
            <div class="col-sm-2">
              <button class="btn btn-default btn-sm" ng-click="tasks.addTask()">Add Task</button>
            </div>
          </div>
        </div>
      </div>
      <div class="panel panel-default" ng-repeat="task in tasks.allTasks">
        <div class="panel-body">
          <div class="checkbox">
            <label>
              <input type="checkbox" ng-model="task.isCompleted">
              <span ng-bind="task.title" ng-class="{strike: task.isCompleted}">Basic panel example</span>
            </label><span class='pull-right glyphicon glyphicon-remove' ng-click="tasks.deleteTask(task)"></span>
          </div>
        </div>
      </div>
    </div>
    <script type="text/javascript" src="scripts/vendor/jquery.min.js"></script>
    <script type="text/javascript" src="scripts/vendor/bootstrap.min.js"></script>
    <script type="text/javascript" src="scripts/vendor/angular.min.js"></script>
    <script type="text/javascript" src="scripts/app.js"></script>
    <script type="text/javascript" src="scripts/appServices/shared/fileSystem.js"></script>
    <script type="text/javascript" src="scripts/tasks/tasks.js"></script>
  </body>
</html>

Sollten Sie noch keine Erfahrung mit AngularJS haben, könnten die AngularJS-Direktiven wie ng-app, ng-controller oder ng-repeat noch etwas befremdlich sein. Falls dem so ist, schauen Sie sich doch kurz eine der vielen frei verfügbaren AngularJS-Einleitungen an.

AngularJS-Apps werden immer in Modulen gekapselt, erstellen Sie zunächst eine neue JavaScript-Datei mit dem Namen app.js im Ordner app/scripts/ im Projektverzeichnis und definieren Sie das AngluarJS-Modul wie folgt:

(function(){
  "use strict";
  angular.module('nwTaskify', []);
})();

Listing 3 zeigt den Inhalt der Datei app/scripts/tasks/tasks.js.

(function(){
  "use strict";

  function TaskController($q, fileSystemService){
    var vm = this;

    vm.allTasks = [];
    vm.newTask =  "";

    vm.addTask = function(){
      if(vm.newTask !== ''){
        vm.allTasks.push({title: vm.newTask, isCompleted: false});
        vm.newTask = '';
      }
    };

    vm.deleteTask = function(task){
      // room for improvement :)
      var index = null;
      for (var i = vm.allTasks.length - 1; i >= 0; i--) {
        if(vm.allTasks[i].title === task.title){
          index = i;
        }
      }
      if(index !== null) {
        vm.allTasks.splice(index, 1);
      }
    };

    vm.showRepo = function(){
      gui.Shell.openExternal('https://github.com/thotstenhans/nwTaskify');
    };

  }
  angular.module('nwTaskify').controller('taskController', TaskController);
})();

Dies ist ein einfacher AngularJS-Controller, der die Basisfunktionalitäten für die nwTaskify-App bereitstellt. Neben den Eigenschaften für die Datenbindung werden die Methoden addTask() und deleteTask(task) auf dem ViewModel veröffentlicht. Diese Methoden wurden in der index.html bereits mittels ng-click an die entsprechenden UI-Elemente gebunden.

Automatisierung mit gulp

Bis jetzt wurde lediglich eine Webanwendung entwickelt, doch mittels einfacher Mittel kann aus dieser Webanwendung eine NW.js-App für alle Desktopplattformen erstellt werden. Hierzu wird gulp benötigt. gulp ist ein JavaScript basierter Task-Runner, der über ein enormes Plug-in-Ökosystem verfügt.

#gulp installieren
~/nwtaskify/npm install gulp-cli –g
~/nwtaskify/npm install gulp --save-dev
~/nwtaskify/npm install del --save-dev
~/nwtaskify/npm install node-webkit-builder --save-dev

Neben gulp selbst werden an dieser Stelle noch zwei weitere Plug-ins installiert; – del und node-webkit-builder. del werden verwendet, um vor dem Build der NW.js-App das Zielverzeichnis zu leeren. node-webkit-builder dient dazu, um automatisiert eine NW.js-Anwendung zu erstellen. Sicherlich kann diese Task auch manuell erledigt werden, aber die einmalige Automatisierung spart im weiteren Verlauf der Entwicklung viel Zeit und ist weniger fehleranfällig.

Schauen Sie sich die Task build in Listing 4 genau an.

var gulp   = require('gulp'),
    rename = require('rename'),
    del    = require('del'),
    NodeWebkitBuilder = require('node-webkit-builder'),
    runSequence       = require('run-sequence');

gulp.task('clean', function(callback){
  del('build', callback);
});

gulp.task('copy-vendor-js', function(){
  return gulp.src(['bower_components/angular/angular.min.js', 'bower_components/jquery/dist/jquery.min.js', 'bower_components/bootstrap/dist/js/bootstrap.min.js'])
         .pipe(gulp.dest('app/scripts/vendor'));
});

gulp.task('copy-vendor-css', function(){
  return gulp.src('bower_components/bootstrap/dist/css/bootstrap.min.css')
         .pipe(gulp.dest('app/styles/vendor'));
});

gulp.task('copy-vendor-fonts', function(){
  return gulp.src('bower_components/bootstrap/dist/fonts/*.*')
        .pipe(gulp.dest('app/styles/fonts'));
});

gulp.task('default', function(callback){
  runSequence('clean', ['copy-vendor-js', 'copy-vendor-css', 'copy-vendor-fonts'], 'build', callback);
});

gulp.task('build', function(callback){
  var nw = new NodeWebkitBuilder({
    files: 'app/**/*',
    version: '0.12.2',
    platforms: ['osx64', 'win32', 'linux64'],
    cacheDir: process.env.HOME + "/.nwjs-cache",
    buildDir: './build'
  });

  nw.build()
    .then(function(){
    callback();
  }).catch(function(error){
    callback(error);
  });
});

Hier wird das node-webkit-builder-Plug-in instruiert wie die NW.js-Anwendung gebaut werden soll. Über die Eigenschaft platforms kann man angeben, für welche Betriebssysteme die Anwendung kompiliert werden soll. win32, win64 für Windows, osx32, osx64 und linux32, linux64 können hier verwendet werden, um für sämtliche Desktopplattformen direkt zu kompilieren. Auch an dieser Stelle finden wir wieder eine version-Property. Wenn Sie hier eine Version angeben, wird die Node-Webkit-Builder-Task versuchen, eine entsprechende NW.js-Version zu finden, um Ihre App auf dieser Grundlage zu erstellen. Geben Sie allerdings keinen Wert für die Eigenschaft version an, wird der Standardwert latest verwendet. Eine Auflistung der verfügbaren Versionen finden sie im NW.js-Wiki.

Die weiteren Eigenschaften der Task sind mehr oder weniger selbsterklärend. Allerdings sollten Sie die gute Dokumentation beachten. Je nachdem, auf welcher Plattform Sie entwickeln, gibt es zusätzliche, aber optionale Abhängigkeiten. So können Sie zum Beispiel das Anwendungsicon für Windows nur von einem Mac OS X setzen, wenn Sie Wine auf dem System installiert haben.

Wichtig zu erwähnen ist noch die optionale Eigenschaft cacheDir. In dieses Verzeichnis werden die NW.js Executables vor dem Build-Prozess heruntergeladen. Sollten Sie mehrere Apps mit NW.js planen, empfiehlt es sich, hier ein lokales Verzeichnis für alle Projekte anzugeben. Erstens sparen Sie Bandbreite für den Download der jeweiligen NW.js-Pakete, zum anderen sparen Sie sehr viel Zeit beim Build-Prozess, weil das Laden aus dem Dateisystem logischerweise um ein Vielfaches schneller ist als der jeweilige Download. Sicherlich können Sie für jeden beliebigen Ordner für die Eigenschaft cacheDir angeben, es empfiehlt sich allerdings, einen Ordner innerhalb des aktuellen Nutzerverzeichnisses zu wählen. Dadurch haben Sie auch später keinerlei Probleme, wenn Sie den Build automatisiert auf einem Build-Server ausführen. Listing 4 zeigt, dass cacheDir auf einen Ordner unterhalb des aktuellen Nutzerverzeichnisses zeigt.

Nun ist es an der Zeit für einen ersten Test. Speichern Sie an dieser Stelle alle Dateien ab und wechseln Sie wieder in eine Kommandozeile oder in Ihr Terminal. Führen sie das gulpfile mittels gulp im Projektverzeichnis aus. Sie sollten nach der Fertigstellung einen Ordner build im Projektverzeichnis finden. Darin befinden sich die ausführbaren Dateien für jede angegebene Plattform. Starten Sie die App für Ihre aktuelle Plattform. Das Ergebnis sollte je nach Plattform der Abbildung 1 entsprechen.

Abb. 1: Der erste Schritt

Abb. 1: Der erste Schritt

Erweiterte App-Einstellungen

Wenn Sie sich das aktuelle Ergebnis angeschaut haben, sollten Sie feststellen, dass Sie als Anwender noch die Möglichkeit haben, die Adresse innerhalb der NW.js-App anzupassen und über den Settings-Button in der Toolbar die Chrome-Developer-Tools starten zu können. Für einen produktiven Einsatz sollten diese „Features“ noch deaktiviert werden. Öffnen Sie hierzu die Datei app/packages.json und passen Sie das Ergebnis wie in Listing 5 gezeigt an.

{
  "name": "nwTaskify",
  "main" : "./index.html",
  "version": "0.0.1",
  "window": {
    "toolbar" : false,
    "width": 900,
    "height": 700 }
}

toolbar definiert, ob die Chrome-Toolbar sichtbar ist oder nicht (inklusive der Developer-Tools). Via height und width werden die initialen Dimensionen der App bestimmt. Eine komplette Auflistung aller verfügbaren Properties finden Sie auf GitHub.

Plattformspezifische Features

Wenn Sie unsere nwTaskify-App unter Mac OS X verwenden, könnte Ihnen ebenfalls aufgefallen sein, dass die Standard-Shortcuts wie CMD+A, CMD+C, CMD+V oder auch CMD+Q (zum Schließen der Anwendung) nicht funktionieren. Damit die OS-X-nativen Plattformfeatures funktionieren, muss ein App-Menü bereitgestellt werden. Hier bietet NW.js ein kleines API, das verwendet werden kann, um Menüs in Anwendungen plattformübergreifend oder aber plattformspezifisch bereitzustellen. NW.js kapselt die UI-relevanten APIs in ein Node.js-Modul namens nw.gui:

var nativeMenuBar = new gui.Menu({type: "menubar"});
var mainWindow = gui.Window.get();
nativeMenuBar.createMacBuiltin(mainWindow.title);
mainWindow.menu = nativeMenuBar;

Das Hinzufügen des App-Menüs ist eine Aufgabe, die während des Anwendungsstarts durchgeführt werden sollte, damit der Anwender nicht bemerkt, wie das Menü hinzugefügt wird. In AngularJS-Apps bietet sich ein run-Block an, um solche Initialisierungslogik auszuführen. Das Codefragment aus Listing 6 zeigt die neue Version der Datei app/scripts/app.js:

(function(){
  "use strict";

  angular.module('nwTaskify', [])
    .run(function($log){
    var gui = require('nw.gui');
    var mainWindow = gui.Window.get();
    if(process.platform == "darwin" ) {
      var nativeMenuBar = new gui.Menu({type: "menubar"});
      try {
        nativeMenuBar.createMacBuiltin(mainWindow.title);
        mainWindow.menu = nativeMenuBar;
      } catch (ex) {
        $log.error('Error adding OSX default menu -' + ex.message);
      }
    }
  });
})();

Import und Export

Betrachten Sie sich jetzt die Liste der zu erstellenden Use Cases, werden Sie merken, dass noch zwei Use Cases fehlen: Mit nwTaskify soll es möglich sein, Aufgaben zu exportieren und aus einer vorhandenen Datei erneut zu importieren. Hier kommen gleich mehrere Dinge zum Tragen. Zunächst benötigt unsere App sowohl einen FileSave– als auch einen FileOpen-Dialog, damit der Anwender selbst bestimmen kann, wo die Dateien für Im- und Export landen sollen. Danach muss die eigentliche Interaktion mit dem Filesystem noch implementiert werden. Hierzu wird das Standard-Node.js-Paket fs verwendet.

Native Filedialoge werden mit NW.js über <input type=“file“ />-Elemente realisiert. Ein natives JavaScript-API hierzu gibt es aktuell nicht. Erweitern Sie die index.html hierzu und ersetzen Sie den HTML-Kommentar <!– IMPORT / EXPORT –> durch den HTML-Code aus Listing 7.

<li><a href="#" ng-click="tasks.import()">Import Tasks</a></li>
<input style="display:none;" id="fileImportTasks" type="file" accept=".json" />
<li role="separator" class="divider"></li>
<li><a href="#" ng-click="tasks.export()">Export Tasks</a></li>
<input style="display:none;" id="fileExportTasks" type="file" nwsaveas="my-tasks.j
 son" />

NW.js bietet für File-Save-Dialoge eine neue HTML-Direktive namens nwsaveas, die verwendet wird, um den Standarddateinamen direkt in HTML zu definieren. Aufgrund der Tatsache, dass beide Input-Felder nicht sichtbar sind, muss noch etwas Arbeit im JavaScript-Code erledigt werden. Passen Sie den Controller in app/scripts/tasks/tasks.js dahingehend an, dass neue Abhängigkeiten injiziert werden: $q und fileSystemService (um den fileSystemService kümmern wir uns umgehend). In Listing 8 geschieht nun einiges.

(function(){
  "use strict";

  function TaskController($q, fileSystemService){
    var choosers = {
      import : "#fileImportTasks",
      export : "#fileExportTasks"
    };
    var gui = require('nw.gui');
    var vm = this;

    vm.allTasks = [];
    vm.newTask =  "";

    var chooseFile = function(selector){
      var d = $q.defer();
      var chooser = angular.element(selector)[0];

      chooser.onchange = function(event){
        var path = this.value;
        this.value = '';
        d.resolve(path);
      };
      chooser.click();
      return d.promise;
    };

    vm.export = function(){
      var onFileChoosen = function(path){
        return $q.when({
          path: path, 
          tasks: vm.allTasks
        });
      };
      chooseFile(choosers.export)
        .then(onFileChoosen)
        .then(fileSystemService.exportTasks);
    };

     vm.import = function(){
      var onTasksLoaded = function(tasks){
        vm.allTasks = tasks;
      };
      chooseFile(choosers.import)
        .then(fileSystemService.importTasks)
        .then(onTasksLoaded);
    };

    vm.addTask = function(){
      if(vm.newTask !== ''){
        vm.allTasks.push({title: vm.newTask, isCompleted: false});
        vm.newTask = '';
      }
    };

    vm.deleteTask = function(task){
      // room for improvement :)
      var index = null;
      for (var i = vm.allTasks.length - 1; i >= 0; i--) {
        if(vm.allTasks[i].title === task.title){
          index = i;
        }
      }
      if(index !== null) {
        vm.allTasks.splice(index, 1);
      }
    };

    vm.showRepo = function(){
      gui.Shell.openExternal('https://github.com/thotstenhans/nwTaskify');
    };
  }
  angular.module('nwTaskify').controller('taskController', TaskController);
})();

Es wurden die beiden Methoden import() und export() definiert. Damit der Code für die Filedialoge nicht doppelt geschrieben werden muss, wurde dieser in die private Methode chooseFile() gekapselt. $q wird verwendet, damit unser Controllercode korrekt benachrichtigt wird, sobald der Anwender eine Datei ausgewählt oder das Ziel für den Import gewählt hat.

Abschließend fehlt noch der fileSystemService. Erstellen Sie das File in app/scripts/appServices/shared . Node.js-Module können wie üblich in Node.js einfach mittels require() geladen werden. Das macht die Interaktion mit dem Filesystem denkbar einfach. Wie bereits im Controller zu sehen war, muss der FileSystemService lediglich zwei Methoden bereitstellen. Die Methode importTasks(fileName) dient zum Importieren von Tasks aus einem gegebenen Pfad. Auf der anderen Seite wird die Methode exportTasks(exportInfo) dazu verwendet, um alle aktuellen Tasks als JSON-String in die angegebene Datei zu schreiben. exportInfo ist hierbei ein einfaches JSON-Objekt, das zuvor im tasksController erstellt wurde; es beinhaltet neben den eigentlichen Tasks noch den Zielpfad, der vom User ausgewählt wurde (Listing 9).

(function(){
  "use strict";

  function FileSystemService($q){
    var fs = require('fs');

    this.exportTasks = function(exportInfo){
      var d = $q.defer();

      fs.writeFile(exportInfo.path, JSON.stringify(exportInfo.tasks), function(err){
        if(err){
          d.reject(err);
        }else{
          d.resolve(true);
        }
      });
      return d.promise;
    };

    this.importTasks = function(fileName){
      var d = $q.defer();
      fs.readFile(fileName, function(err,data){
        if(err){
          d.reject(err);
        }else{
          d.resolve(JSON.parse(data));
        }
      });
      return d.promise;
    }
  }
  angular.module('nwTaskify').service('fileSystemService', FileSystemService);
})();
Abb. 2: Resultat auf Windows XP, Mac OS X und Ubuntu-Linux

Abb. 2: Resultat auf Windows XP, Mac OS X und Ubuntu-Linux

Somit wären an dieser Stelle sämtliche Use Cases auf einer Codebasis implementiert, und die nwTaskify-App kann problemlos auf den drei mächtigsten Desktopplattformen ausgeführt werden. Die Tatsache, dass Ihre App problemlos unter beispielsweise Windows XP ausgeführt werden kann, ist auch für einige Firmen heute noch ein sehr wichtiges Argument. Mächtige Frameworks wie das .NET Framework 4.5.1 werden nämlich auf dieser – doch etwas in die Tage gekommenen – Plattform nicht mehr unterstützt. Dennoch findet man XP noch in der weiten Wildnis.

Natürlich bietet NW.js weitere Möglichkeiten an, um Ihre Anwendungen in die Betriebssysteme zu integrieren. Verschaffen Sie sich abschließend einfach einen Überblick sämtlicher Integrationsmöglichkeiten im ausführlichen NW.js-Wiki, das Sie auf GitHub finden können, und runden Sie damit Ihr Wissen ab. Viel Spaß!

Fazit

Anhand des kleinen Beispiels nwTaskify konnten Sie in diesem Artikel sehen, wie schnell und einfach man aus einer Web-App eine echte Desktopanwendung erstellt, die auf vielen Plattformen ausgeführt werden kann. Abbildung 2 zeigt abschließend das Ergebnis auf Windows XP, Mac OS X Yosemite und Ubuntu-Linux. Das Potenzial von NW.js und die damit einhergehende Einfachheit macht die Überlegung, ob man heute Cross-Plattform-Desktop-Apps entwickelt oder nicht, nahezu unnötig. Das schlanke, aber dennoch gut dokumentierte API von NW.js trägt hierzu sicherlich auch seinen Teil bei. Den gesamten Sourcecode der nwTaskify-App finden Sie auf GitHub.

Windows Developer

Windows DeveloperDieser Artikel ist im Windows Developer erschienen. Windows Developer informiert umfassend und herstellerneutral über neue Trends und Möglichkeiten der Software- und Systementwicklung rund um Microsoft-Technologien.

Natürlich können Sie den Windows Developer über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist der Windows Developer ferner im Abonnement oder als Einzelheft erhältlich.

Aufmacherbild: Group of Business People Using Digital Devices von Shutterstock.com / Urheberrecht: Rawpixel

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -