Mit JavaScript wie hausgemacht: NativeScript
Seite 2: Native Umsetzung
Und nun nativ
Das NativeScript-Projekt ist damit zwar technisch komplett, aber noch nicht ausführbar. Das liegt daran, dass es noch plattformagnostisch ist. Um die konkrete Anbindung an eine Plattform einzufügen dient wieder ein im Projektordner ausgeführter TNS-Befehl, der für Android folgendermaßen aussieht: tns platform add android.
Nach Eingabe des Befehls muss NativeScript einige Megabyte an Daten von einem langsamen Server herunterladen. Die Abarbeitung nimmt somit einige Zeit in Anspruch.
Gelegentlich stellt add plattform die Arbeit kommentarlos ein, wenn der Befehl mit seiner Ausführungsumgebung unzufrieden ist. Nach erfolgreicher Ausführung weist der Unterordner Plattforms ein Verzeichnis namens Android auf, in dem sich ein komplettes Android-Projekt befindet.
Damit ist der Code bereit zur Installation. Die in NativeScript implementierte CLI nimmt Entwicklern die Arbeit ab: Das Kommando tns device listet alle mit der Workstation verbundenen Telefone auf.
Der Befehl tns run android installiert die Anwendung. Beim ersten Aufruf erfolgt der Download eines rund 20 MByte großen Archivs mit dem Gradle-Buildsystem von einem Telerik-Server.
Während der Programmausführung gibt tns run android Logdaten aus, die Entwickler über den Console.log-Befehl um eigene Meldungen ergänzen können. In der praktischen Nutzung ist run android nur für die Erstauslieferung der Applikationen verantwortlich. Zudem kommt das Kommando zum Einsatz, wenn sich der native Unterbau wie die Manifestdatei ändern.
Für Änderungen im JavaScript- oder XML-Teil dient im Normalfall der im NativeScript implementierte LiveSync-Dienst. Er erkennt Änderungen im Dateisystem und leitet sie an das Telefon weiter. Seine Aktivierung erfolgt durch die Eingabe von tns livesync --watch – einen eventuell laufenden tns run android-Befehl beendet zuvor der Druck auf Ctrl+C.
Das funktioniert jedoch nicht unter Android 4.3. Wer LiveSync nutzen möchte, muss auf Android 4.4 umsteigen. Auch einige Samsung-Telefone verweigern die Kooperation. In beiden Fällen schafft tns run Abhilfe.
Formular im Austausch
Der nächste Schritt ist ein Formularwechsel. Dazu benötigt die Applikation die folgenden drei zusätzliche Dateien:
- work-page.js
- work-page.xml
- work-view-model.js
Dabei darf es sich um Kopien des vorhandenen Formulars handeln. Der nächste Schritt passt den Inhalt von main-page.xml folgendermaßen an, um zwei Textfelder zu erzeugen:
<StackLayout>
<TextField id="email" hint="Email Address"
keyboardType="email" autocorrect="false" />
<TextField id="password" hint="Password"
autocorrect="false" />
<Button text="Login" tap="{{ onTap }}" />
</StackLayout>
Interessant ist dabei vor allem das bisher noch nicht vorgestellte ID-Attribut. Es dient dem Auffinden des Steuerelements zur Laufzeit und gibt einen Verweis auf die jeweilige Instanz zurück.
Wer das Programm in der vorliegenden Form durch Speichern der Datei aktualisiert, erhält den in Abbildung 3 gezeigten Formularaufbau.
Für die eigentliche Aktivierung des Formulars ist eine Änderung im Modell der Haupt-View erforderlich. Der relevante Teil von main-view-model.js sieht anschließend folgendermaßen aus:
var frameModule = require("ui/frame");
...
viewModel.onTap = function() {
var topmost = frameModule.topmost();
topmost.navigate("work-page");
}
NativeScript stellt Formulare in Containern dar, die Frame heißen. Die Routine ergreift den sichtbaren Container, um ihn mit neuem Inhalt zu füllen. Für die Beschaffung des statischen frameModule-Objekts kommt die von anderen Node-Frameworks bekannte require()-Methode zum Einsatz. Damit ändert das Programm das aktive Formular, wenn ein Nutzer den Login-Button klickt.
An dieser Stelle gibt es zudem die Möglichkeit, einen kleinen Blick auf das in NativeScript implementierte Fehlerhandhabungssystem zu werfen. Die Suche nach der neuen Datei erfolgt durch einen klassischen Dateinamensvergleich: Wenn die Runtime die betreffende Datei nicht findet, handelt es sich um einen gravierenden Fehler. NativeScript reagiert mit dem in Abbildung 4 gezeigten Bildschirm. Wer den kompletten Stacktrace einsehen will, muss ihn in die Zwischenablage kopieren und in einen beliebigen Texteditor einfügen.
Die in den beiden Textfeldern eingegebenen Werte stehen der Engine – nicht jedoch dem nächsten Formular – zur Verfügung. Die Übergabe lässt sich durch Anpassung des Navigationsbefehls erreichen. Er nutzt nun statt des Dateinamens ein komplexes Objekt, das zusätzlich Kontextparameter übergeben kann:
var viewModule = require("ui/core/view");
var email;
var password;
function createViewModel() {
var viewModel = new Observable();
viewModel.onTap = function() {
var topmost = frameModule.topmost();
email = viewModule.getViewById(topmost, "email");
password = viewModule.getViewById(topmost, "password");
Interessant ist in dem Beispiel die von Android bekannte Nutzung der Methode getViewById. Sie nimmt einen Suchkontext und eine ID entgegen und liefert im Erfolgsfall einen Verweis auf das dazu passende Steuerelement. Das Beispielformular nutzt die Informationen zum Füllen des Kontextobjekts:
var navigationEntry = {
moduleName: "work-page",
context : {
emailPar : email.text,
passwordPar: password.text
}
};
topmost.navigate(navigationEntry);
}
return viewModel;
}
Da das manuelle Herumschieben von Steuerlementinformationen nicht praktikabel ist, kommt Data Binding zum Einsatz. Für die Engine relevante Attribute werden in NativeScript durch zwei geschweiften Klammern angezeigt – der Korpus von work-page.xml verbindet die Texte der beiden Labels mit den Attributen, die in der Engine liegen:
<StackLayout>
<Label text="{{email}}" />
<Label text="{{password}}" />
</StackLayout>
Zum besseren Verständnis folgt nun ein kleiner Umweg: Der Code von work-view.js bekommt einen Handler für das navigateTo-Event, der das Kontextobjekt und somit den vom anderen Formular übergebenen Parametern erhält:
var createWorkModel =
require("./work-view-model").createWorkModel;
function onNavigatingTo(args) {
var page = args.object;
page.bindingContext = createWorkModel();
page.on("navigatedTo", function (eventData) {
page.bindingContext.email = page.navigationContext.emailPar;
page.bindingContext.password =
page.navigationContext.passwordPar;
});
}
exports.onNavigatingTo = onNavigatingTo;
Die in der Datei work-view-model.js liegende Modellklasse hat die Aufgabe, das Modell zu konfigurieren. Für das Beispiel erhält sie den Wert 42 – er ist während der Programmausführung leicht erkennbar:
var Observable = require("data/observable").Observable;
function createWorkModel() {
var viewModel = new Observable();
viewModel.email = 42;
viewModel.password = 42;
return viewModel;
}
exports.createWorkModel = createWorkModel;
Damit ist die Applikation rein theoretisch bereit zum Ausführen. Nach der Installation auf dem Telefon und der Eingabe beliebiger Werte in die Textboxen, erscheint trotzdem immer das in Abbildung 5 gezeigte Ergebnis.