easy Datenbindung MVVM und XAML

Ich erinnere mich noch recht gut an meine ersten Silverlight Schulungen. Das Thema zwei Wege Datenbindung und die Benachrichtigungsevents ans Userinterface, war für den Kursteilnehmer immer etwas mühsam. Nun komme ich aus einem Vortrag auf der #ADCX und hab ganz Beiläufig fody kennen gelernt. Und die Welt ist schöner geworden.

Zur Erinnerung: Um einem Model die Fähigkeit zu geben, eine Änderung der Daten dem User Interface mitzuteilen, muss das Interface INotfiyPropertyChanged implementiert werden. Pro Eigenschaft muss dann das ProperChanged Event, mit dem Namen des Propertys als String Argument, ausgelöst werden. Mal abgesehen von der Tipperei, eine perfekte Fehlerquelle. Mit .net 4.0 wurde dann das CallermemberAttribut eingeführt, das dem Compiler erlaubt, den Wert zuzuweisen. In Silverlight 5 muss man einen Trick verwenden, um das Attribut überhaupt nutzen zu können.

   1:  <AttributeUsageAttribute(AttributeTargets.Parameter, Inherited:=False)>
   2:  Public NotInheritable Class CallerMemberNameAttribute
   3:      Inherits Attribute
   4:  End Class

Ohne obigen Code wird folgender VB.NET Code in einem Silverlight Projekt nicht kompilieren

   1:  Public Class person
   2:      Implements INotifyPropertyChanged
   3:      Property id As Integer
   4:      Private _name As String
   5:      Public Property name() As String
   6:          Get
   7:              Return _name
   8:          End Get
   9:          Set(ByVal value As String)
  10:              _name = value
  11:              OnPropertyChanged()
  12:          End Set
  13:      End Property
  14:   
  15:      Protected Sub OnPropertyChanged(<CallerMemberName> 
Optional propertyName As String = Nothing)
  16:          RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
  17:      End Sub
  18:      Public Event PropertyChanged(sender As Object, 
e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
  19:  End Class

 

Die Zwei Wege Bindung wird nur für das Property name implementiert. Es muss die Langform mit Getter und Setter verwendet werden um in letzteren das Änderungs Event auszulösen (Zeite 11). Der Compiler ersetzt in Zeile 11 den Wert des Parameters propertyName mit “name”.

Es gib wohl Menschen die sich den Gedanken der Compiler Services noch tiefer zu Gemüte geführt haben. Auf Basis von Fody existiert ein Plugin für NotifyPropertyChanged, das man sich per Nuget in seine Visual Studio Projekt installieren kann.

image

Ab jetzt ist es nur noch geli einfach. Aus je zehn Zeilen Code wird eine. Es reicht eine Klassenattribut einzufügen und die verkürzte Propertysyntax.

   1:  <ImplementPropertyChanged>
   2:  Public Class person
   3:      Property id As Integer
   4:      Property name As String
   5:  End Class
Der Compiler erzeugt daraus im wesentlichen die Langversion des Codes, wie im vorigen Beispiel demonstriert.

PS fast auf den Tag genau vier Jahre sind vergangen seit Bob Muglia beiläufig Silverlight für Tod erklärte. Nur drei Monate später musste Bob der damalige Chef von Server und Tools seinen Stuhl räumen. HTML5 reicht auch heute noch bei weitem nicht an Silverlight heran.

Expression Blend UX Guides

Es ist wahnsinnig einfach schlechtes Design zu entwerfen. Aber es ist auch relativ einfach gute User Interfaces zu bauen, wenn man die Grundregeln kennt. Um ein klares Layout (Clean Layout) zu erstellen braucht das UI ein Schema. Mit einem typografischen Grid Layout kann man die UI Elemente wie Text und Bilder strukturieren und ausrichten.

(Quelle Wikipedia)

Visual Studio 2013 erlaubt es ein Raster einzublenden und neu erzeugte Controls an den unzähligen Führungslinien auszurichten.

image

Expression Blend für Visual Studio 2013 kann da schon einiges mehr. Wenn man im Designer vom Lineal per Maus Drag und Drop auf das UI zieht entstehen blaue Führungslinien. Diese werden Rulers und Guides genannt.

image

Dieses UI Schema kann man sogar speichern um es später in anderen XAML Seiten wieder zu verwenden. Im Menü View – Manage Guides auswählen.

 image

Besonders praktisch, Blend bietet auch eine Reihe von vordefinieren Guidelines  zur Auswahl. Für eine Windows Store App (vormals METRO) sind das

  • BasicPage
  • FileOpenPicker
  • GroupDetailPage
  • GroupedItemsPage
  • HubPage
  • ItemsPage
  • SearchResultsPage
  • SettingsFlyout
  • ShareTargetPage
  • SplitPage

Die GroupItemsPage Vorlage

image

Expression Blend für Visual Studio 2013 ist der Nachfolger von Blend 4.0. Damit lassen sich WPF, Silverlight und WIndows 8.1 Apps (HTML und XAML) Designen. Expression Studio 4 wurde komplett eingestellt. Der Nachfolger ist direkt in Visual Studio integriert und benötigt keine separate Lizenz. Mehr zu UX Design gibt es auf der GUI&DESIGN Konferenz in Berlin. Der Autor gibt auch 2-tägige Blend Schulungen.

Layoutrounding mit Expression Blend

Pixel sind niemals rund. Wenn man es genau nimmt sind die realen Pixel quadratisch. Es gibt eine Reihe von Fällen indem die Umrechnung der virtuellen Pixel in die physikalisch vorhandenen erhebliche Probleme aufwirft. Man kann keinen halben Pixel füllen. Windows versucht alles erdenkliche solche Effekte zu minimieren. Dazu kommt das mit dem High DPI Displays zumindest dieses Problem, für das ungeübte Auge nicht zu erkennen ist. Die Funktion nennt sich Layoutounding. Damit wird ein mathematisch halb zu füllendes Pixel ganz gefüllt.

In XAML bestimmt das Container Element wie die Inhalte gerendert werden. Silverlight macht dies automatisch, in WPF ist diese Funktion deaktiviert. Folgendes WPF Beispiel zeigt di Problematik in Expression Blend für Visual Studio 2013.

   1:  <Canvas x:Name="LayoutRoot" Background="White"  UseLayoutRounding="True">
   2:  <Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="10" 
   3:  Stroke="Black" VerticalAlignment="Top" Width="10" 
StrokeThickness="0.2" Canvas.Left="245" Canvas.Top="174"/>
   4:  <Ellipse Fill="#FF1717F1" HorizontalAlignment="Left" Height="8" 
   5:  StrokeThickness="0.2" VerticalAlignment="Top" Width="8" 
Canvas.Left="246" Canvas.Top="175"/>
   6:  </Canvas>

Es gibt einen Aussenkreis mit nur 1 Pixel Abstand, der mit einer 0,2px Linie umrandet ist. Nimmt man Silverlight als Projektype sieht das Ergebnis im Visuellen Designer wie erwartet aus. Hier im Bild auf 6000% gezoomt.

layoutrounding2

Alles wie es sein soll. Wenn man den gleichen XAML Code in ein WPF Projekt kopiert und das Attribut UseLayoutRounding  nicht oder auf false gesetzt hat sieht das Ergebnis wie folgt aus

layoutrounding1

Dies mag auf den ersten Blick ein sehr konstruiertes Beispiel sein, soll aber verdeutlichen, das auch bei WPF Pixelgenaues Layout besonderer Aufmerksamkeit bedarf.

Themen wie diese, können Sie auf der GUI&DESIGN Konferenz mit den Experten diskutieren.

Angular Controller as

Wenn ich so darüber nachdenke, gibt es in Angular.js mindestens immer zwei Möglichkeiten eine Aufgabe zu lösen. Eine Direktive kann eine Funktion oder ein JSON Objekt zurück liefern. Ein Service kann eine Factory oder ein Service sein. Alles leicht verwirrend. In die Kategorie, muss das auch noch sein, fällt die Scope Freiheit eines Controllers.

Üblicherweise wird jedem Controller der Scope per Dependency Injection üergeben und damit eine Singleton Instand erstellt. Es gib auch eine Alternative Variante ohne $Scope.

Dabei wird eine “Controller as” Syntax eingesetzt, die sozusagen einen Alias erzeugt, der dann als Objektträger herhalten muss.

   1:    <div ng-controller="mycontroller as hannes">
   2:          {{hannes.count}}
   3:          <button ng-click="hannes.click()">plus</button>
   4:    </div>
   5:    

 

Der zugehörige Controller wird aber gänzlich anders codiert. Zunächst wird wie üblich die App als Modul erzeugt und darin ein Controller definiert, der auf eine Controller Methode verweist. Dieser ist allerdings Parameterlos. Die Propertys des Scopes werden per this erzeugt. Das fühlt sich aus C# Entwicklersicht fast natürlich an.

JavaScript typisch, wird per Prototype das bestehende Objekt um eine Funktion, hier click, erweitert.

   1:    var App = angular.module('App', []);
   2:    App.controller('mycontroller', myfunction)
   3:    function myfunction() {
   4:        this.count = 0;
   5:      };
   6:   myfunction.prototype.click= function () {
   7:       this.count++;
   8:     };

 

Auch wenn ich die Variante mit $scope optisch weniger gelungen finde, werde ich wegen der Konsistenz innerhalb von Angular diesen Weg nicht verwenden.

Fileupload mit Angular.JS

Eigentlich müsste es heißen: Der Kampf des Dateiuploads gegen Angular.JS. Das Framework unterstützt uns dabei in keiner Weise. Wir müssen es an mehreren Stellen sogar austricksen.

Die Programmieraufgabe umfasst folgende Bauteile

  • Webservice, der die Datei entgegennimmt und speichert
  • HTML User Interface mit Input Type File
  • JavaScript Code für Ajax und Scope

Um volle Kontrolle über die hochgeladenen Dateien zu haben, bezüglich Typ, Größe oder Speicherort, bleibt nur ein Service am Web Server. In diesem Fall ist dieser per ASP.NET Web API und VB.NET erstellt.

   1:  Public Class fileuploadController
   2:      Inherits ApiController
   3:      <HttpPost>
   4:      Public Sub UploadFile()
   5:          If HttpContext.Current.Request.Files.AllKeys.Any() Then
   6:              Dim f = HttpContext.Current.
Request.Files("UploadedImage")
   7:              If f IsNot Nothing Then
   8:                  Dim fp = Path.Combine(
HttpContext.Current.Server.MapPath("~/upload"),
Path.GetFileName(f.FileName))
   9:                  f.SaveAs(fp)
  10:              End If
  11:          End If
  12:      End Sub
  13:  End Class

 

Das UX wird aus optischen Gründen auf einen Button reduziert, der den Betriebssystem-File-Open-Dialog öffnet und beim Schließen den Upload anstößt. Dadurch können wir das UI per Bootstrap schöner stylen, da die leere Textbox des Type File nicht angezeigt wird. Allerdings muss ein zweiter Button das Click Event des Fileupload-Buttons auslösen.

Das geschieht in Zeile 6 und zeigt bereits eine Reihe von Problemen mit Angular. Eigentlich sollte der Onclick-Handler nicht im HTML-Code auftauchen. Die Alternative ist eine Angular-Direktive mit rund 15 Zeilen Code, mit der dann auch noch ein zusätzliches Attribut in das Input-Element gelegt werden muss. Das ist mehr Aufwand und der Code ist dadurch leider auch nicht besser lesbar.

Außerdem kann man leider die DOM-Selector-Eigenschaften nicht verwenden, auch wenn ein angular.element(‘#id’) uns dies glauben lässt. Damit dieser Code funktioniert, muss erst noch jQuery eingebunden werden, da JQLite diese Selektoren nicht beinhaltet. Dann könnte man aber gleich $(‘#id’) nutzen. Es drängt sich die Frage auf, warum das komplette jQuery Framework überhaupt Overhead erzeugen soll, wenn es die benötigte JavaScript-Funktion bereits gibt. Also wird hier document.querySelector verwendet, um das Click Event durchzureichen.

Noch schlimmer wird es, wenn man aus normalem JavaScript auf das Angular Framework zugreifen muss. Dies ist hier nötig, da die Value Eigenschaft, die die ausgewählte Datei beinhaltet, nicht per ng-model bindbar ist. Auch für dieses Problem lässt sich eine umfangreiche Direktive schreiben oder einfach das OnChange-Event auf ein Event des $Scope durchreichen (Zeile 4)

   1:  <body  ng-controller="uploadController">
   2:      <label for="fileUpload"></label>
   3:      Select <input id="fileUpload" type="file" 
style="visibility:hidden;position:absolute;top:0;left:0"
   4:         onchange="angular.element(this).scope().onChange(this)" />
   5:   
   6:      <input id="btnUploadFile" type="button" value="Upload File"
onclick="document.querySelector( '#fileUpload').click()" />
   7:  <img id="progress" src="img/upload-indicator.gif"  ng-show="warten"/>

 

image

Zusätzlich wird noch ein animiertes GIF verwendet, das die Uploadaktivität visualisiert und hier durchaus elegant per ng-show an die Eigenschaft Warten des $scope gebunden wird.

Zuletzt muss noch die eigentliche Upload-Logik geschrieben werden. Dazu wird ein Formdata-Objekt erstellt und mit den Datei-Meta-Informationen und dem Stream gefüllt. Dann wird per $http der Post auf den Upload-Service angestoßen. Um ein Problem mit Angular und dem benötigten Multipart-Formdata-Header zu umgehen, fügen wir in Zeile 17-19 Workaround-Code ein.

   1:  var app=angular.module("App",[])
   2:    .controller("uploadController", function ($scope,$http) {
   3:     $scope.warten = false;
   4:     $scope.onChange = function (dateien)
   5:      {
   6:        $scope.warten = true;
   7:        var file = dateien.files[0];
   8:        var data = new FormData();
   9:        data.append('UploadedImage', file);
  10:        data.append('FileName', file.name);
  11:        data.append('fileSize', file.size);
  12:        data.append('fileType', file.type);
  13:   
  14:        $http
  15:          .post('/api/fileupload/uploadfile' , data,
  16:                 {
  17:                   transformRequest: angular.identity,
  18:                    headers: { 'Content-Type': undefined }
  19:                  })
  20:                 .success(function () {
  21:                       $scope.warten = false;
  22:                  })
  23:                 .error(function () {
  24:                       $scope.warten = false;
  25:                          alert('error');
  26:                      });
  27:              };
  28:     });

Sie möchten mehr erfahren und in die Programmierung mit AngularJS einsteigen? Dann sind Sie bei diesem AngularJS Training richtig.“

Angular 1.3 .. is not a function

Die seit wenigen Tagen verfügbare Angular.JS 1.3 Version bringt eine Reihe von Änderungen mit sich, die auch in fertigen Anwendungen negative Auswirkungen haben können. In diesem Fall war es ein Code-Beispiel für meinen Angular.JS Workshop auf der ADC X in Mannheim und Wien. Das JavaScript Code Beispiel für eine globale Controller-Funktion liefert eine Fehlermeldung im F12 Browser Developer Tools Command Window.

image

Nicht, dass man damit schönen Code schreibt. Aber vorher funktionierte

   1:   <div ng-app="" ng-controller="personController">
   2:      ...
   3:  </div>
   4:   
   5:  <script>
   6:          function personController($scope) {
   7:              $scope.firstName = "John",
   8:              $scope.lastName = "Doe"
   9:          }
  10:  </script>

 

Jetzt muss ein Angular Modul und ein expliziter Controller darin definiert werden.

Wer unbedingt das alte Verhalten benötigt, kann dies erzwingen per

   1:   angular.module('myModule').config(['$controllerProvider',
function ($controllerProvider) {
   2:                        $controllerProvider.allowGlobals();
   3:          }]);

 

Das ist allerdings nicht besonders sinnvoll. Wenn man schon ein Modul anlegt, ist der Controller auch gleich gecoded.

Das Problem entsteht für Anfänger, da viele Blogs diesen kurzen Weg für Angular Sample Code verwenden.

Angular.JS heilt Krebs

Das könnte man meinen, wenn man Tweets verfolgt. Kaum ein kritisches Wort. Und wenn doch wird der Urheber einmal durch Twitter rauf und runter gebasht. So passiert einem Daniel Steigerwald. Man könnte durchaus Parallelen zu Religionsgemeinschaften ziehen. Kritiker werden darauf verwiesen, eben nicht richtig verstanden zu haben. Der einzige wahre Weg führt durch dieses Framework (und noch 42 andere, die man braucht).

Für die Schmerzen, die man jetzt gerade erleidet, wird eine wundersame Zukunft, oder Jungfrauen, in einem späteren Leben versprochen.

Lebensrealitäten werden ausgeblendet und Absurdes zu State of the Art erhoben. Gestandene Mittfünfziger erscheinen plötzlich im Superman-Kostüm an ihrem Arbeitsplatz in der Bank, um auch mal ein Superheld zu sein.

image

Natürlich nicht zu vergessen, dieser sagenhafte neue Sprit, der 20 Cent mehr kostet an der Tankstelle und bis zu 10% mehr Leistung verspricht. Super Power eben- darunter machen wir's nicht mehr.

image

Derartig ausgestattet flutscht das Leben quasi von selbst. Und den letzten Zweiflern wird der Open Source-getränkte Lappen in den Mund gestopft, um sie zum verstummen zu bringen.

Um meinen Sohn zu zitieren:

“Hallo! gehts noch?”

Angular ist eines von hunderten JavaScript Frameworks. JavaScript ist per Definition immer quelloffen. Es stammt von einer Firma, die ihr Geld mit dem Ausspähen Deiner Daten verdient. Angular ist bei weitem nicht perfekt und fehlerfrei. Es ist schwer zu erlernen. Die meisten Entwickler schreiben nach wie vor keinen Testcode. Es existiert schlicht, weil JavaScript eine geliebte Missgeburt aus dem letzten Jahrtausend ist.

Trotzdem kommt man bei Web-Applikationen nicht daran vorbei. Es wird breit verwendet und hat wirklich nette Funktionen. Aber Angular.js ist keine Silberkugel und heilt keine Programmiererkrankheit. Persönlich finde ich auch, dass man mit Angular und JavaScript im allgemeinen besonders scheusslichen unlesbaren Code schreiben kann.

Wer sich für das Thema aus einer weniger glorifizierenden Perspektive interessiert, kann an meinen beiden ganztägigen #ADCX Hands On Angular Workshops teilnehmen. Am 20.Oktober in Frankenthal http://adc.ms und am 6. November in Wien http://adc.ms/wien.

Lync Meetings, Headset Mikro funktioniert nicht

Seit einiger Zeit fällt mir in Online-Meetings Tastaturgeklappere auf. Ein untrügliches Indiz, dass der andere Teilnehmer kein Headset benutzt. Ich frage dann gerne nach und immer öfter versichert mir mein Gegenüber ein Headset zu benutzen. Für einen schnellen Test kann man das Lautsprechersymbol aus der Taskleiste nutzen. Einfach mit einem harten Gegenstand auf das Mikrophon klopfen.

lync1

In meinem Fall gab es keinen erkennbaren Ausschlag. Also ist das interne Mikro an, obwohl ein Headset in der 3,5 mm-Kombibuchse steckt.

Für einen Aufnahme-Qualitätstest nehme ich gerne die Windows 8.1 (vormals METRO) Recorder App.

lync2

Auch diese liefert das meinen Erwartungen entsprechende Ergebnis.

Die Lösung findet sich an der Buchse. Immer mehr Notebooks haben nur eine Buchse für Kopfhörer und Mikrofon. Ein entsprechender Adapter leitet dann zwar auf einen vierpoligen 3,5 mm-Klinkenstecker, aber das Notebook scheint das Device so nicht richtig zu erkennen.

Die Lösung bringt die Installation des Realtek HD Audio Managers, der beim Einstecken eines Gerätes einen zusätzlichen Dialog anbietet. Um auf den Dialog zu kommen, klicken Sie einfach rechts die Buchse an. Dann wählen Sie: Audioeingang, Mikrofoneingang, Kopfhörer, Audioausgang oder Headset.

lync3

Ich suche noch die Option, beziehungsweise das Setting ohne die Zusatzsoftware.

Globale Variable mit Angular

Ein Set von global gültigen Variablen legt man gerne in eine static oder shared Klasse. Wer bereits mit ASP.NET gearbeitet hat, weiß, dass dies nicht so einfach ist. Noch schwieriger wird es mit Masterpage (Webforms) oder Layout Pages (MVC). In beiden Fällen werden aus mindestens zwei Dateien Vorlagen und Inhalt vereint.

Es erscheint wenig sinnvoll einen einzigen Controller in die Masterpage zu legen und darin die Logik aller Content Pages einzubauen. Folglich wird man einen  Controller für die Masterpage definieren und einen für jede Content Page. Bleibt die Frage, wie diese miteinander sprechen und wie man Daten im Sinne einer globalen Variable austauscht. Wir müssen den Connection-Status einer Websockets-Verbindung an vielen Stellen prüfen können.

Um gemeinsam nutzbaren Code und damit auch eine Variable anlegen zu können, sieht Angular einen Service, Factory oder Provider vor.  Die Factory erhält den Namen global und liefert den Connection Status mit false zurück.

   1:  App.factory('global', function () {
   2:      var global = {
   3:          isConnected: false
   4:      };
   5:      return global;
   6:  });

Per Depedency Injection wird die Factory im Controller verfügbar. Konkret werden alle Objekte in den Funktonsaufruf des Controllers gepackt, hier der $scope für das Viewmodel und die Factory global.

Die Direktive enthält noch eine Command-Funktion, um den Status ändern zu können.

   1:  app.controller('contentController', function($scope,global) {
   2:    $scope.isConnected = global.isConnected;
   3:    $scope.connect=function()
   4:    {
   5:   
   6:      $scope.isConnected=global.isConnected=true;;
   7:    }
   8:  });

Gebunden wird das Angular typischerweise im HTML Code, in der Praxis in der Content Page.

   1:  <div  ng-controller="contentController">
   2:    Content Page Dingens
   3:      
   4:       <p>Status {{isConnected}}!</p>
   5:       <button ng-click="connect()">connect</button>
   6:    </div>

 

In der Masterpage wird ein eigener Controller definiert, der z.B. Elemente im Menübereich bindbar hält. Der JavaScript-Code unterscheidet sich auf den ersten Blick nicht vom vorigen Codebeispiel. Allerdings muss der Main Controller die Änderung in global.isConnected auch mitgeteilt bekommen. Dazu kann man einen Watcher einrichten, der die Änderung des Wertes zum Anlass nimmt, den lokalen Scope neu zu schreiben.

   1:  app.controller('mainController', function($scope,global) {
   2:   
   3:    $scope.isConnected = global.isConnected;
   4:    
   5:     $scope.$watch(
   6:          function(){ return global.isConnected},
   7:   
   8:          function(newVal) {
   9:            $scope.isConnected = newVal;
  10:          }
  11:        )
  12:   
  13:  });

 

Das lauffähige Sample.

Sehr oft sieht man für diese Aufgabenstellung auch einen Ansatz mit Broadcast auf den $rootscope. Dieser erfordert deutlich mehr Code. Alternativ kann man auch im $Rootscope eine Variable nutzen, davor wird jedoch allerseits gewarnt.

Windows METRO Apps Cross Plattform mit WinJS 3

Cross-Plattform-Development ist in aller Munde. Nun steigt Microsoft mit der neuen WinJS-Bibliothek in den JavaScript-Framework-Markt ein. Konkurrenz sind JQuery Mobile, AngularJS oder auch Kendo UI. Das sind alles Themen auf der ADCX Konferenz.

Der erste Blick auf die JavaScript für Windows Bibliothek per Nuget Download in Visual Studio 2013 eröffnet ernüchternde Downloadzahlen: nur 167 in der ersten Woche.

winjs

In meinem Fall hat zwar die Installation scheinbar geklappt, es waren aber keine neuen CSS oder JS Dateien im Projekt angelegt. Erst der Manuelle Download des Zip mit anschließendem Entpacken in das Web-Projekt, hat den Start ermöglicht. Nebenbei bemerkt: der Download kommt von Amazon Web Services.

winjs2:

Die Zeiten haben sich geändert, gut finde ich das nur bedingt.

Als nächstes wird WinJS in eine ASPX Seite eingebunden. Es gibt zwei Design-Varianten: dark und light.

   1:   <link href="Content/ui-dark.css" rel="stylesheet" />
   2:   <script src="Scripts/WinJS.js"></script>

Die Controls sind in einzelne JavaScript-Dateien aufgeteilt und sozusagen modularisiert. Für den Einstieg packe ich die komplette Bibliothek, immerhin unkomprimiert 3,7 MB groß, in die HTML-Seite.

Ans Ende des HTML-Blockes wird der Script Block gecoded. Die Daten kommen per Json aus einem REST ASP.NET Web Api Service und beinhalten die Northwind Customer Daten. Nachdem die Daten geladen sind, wird die Bindung aktualisiert (Zeile 11)

   1:<script>
   2: WinJS.Application.onready = function () {
   3:              var options = {
   4:                  url: '/api/kunden',
   5:                  type: 'GET'
   6:              };
   7: WinJS.xhr(options).done(
   8:      function (result) {
   9:     callback(result.responseText, result.status);
  10:                      //Binding aktivieren
  11:     WinJS.UI.processAll();
  12:                  },
  13:                  function (result) {
  14:                      callback(null, result.status);
  15:                  }
  16:              );
  17:          };
  18:   
  19: WinJS.Application.start();
  20:   
  21: function callback(responseText, status) {
  22:     if (status === 200) {
  23:       var customers = JSON.parse(responseText);
  24:       WinJS.Namespace.define("DataExample", {
  25:          data: new WinJS.Binding.List(customers)
  26:                  });
  27:   
  28:              } else {
  29:   
  30:              }
  31:          }
  32:   </script>

Etwas ungewohnt werden die empfangenen JSON Array Daten in eine bindbare WinJS Liste umgewandelt. Der Namensraum kombiniert mit dem Eigenschaftsnamen, ergänzt um das Schlüsselwort dataSource, wird im HTML Code per win-Options an das Listview Control gebunden.

Das eigentliche Itemtemplate wird extern, das heißt außerhalb des Listview DIV extra deklariert. Die Feldbindung erfolgt per win-bind Attribut.

   1:  <h1>Demo METRO Liste</h1>
   2:  <div class="customerTemplate" 
data-win-control="WinJS.Binding.Template" style="display: none;">
   3:      <div style="height:80px">
   4:          <h4 data-win-bind="textContent: CompanyName"></h4>
   5:     </div>
   6:  </div>
   7:  <div id="basicListView"
   8:     data-win-control="WinJS.UI.ListView"
   9:     data-win-options="{ itemDataSource : DataExample.data.dataSource, 
  10:     itemTemplate: select('.customerTemplate') }">
  11:  </div>

Richtig erstaunlich wird es im Browser. Die Website sieht aus wie Windows 8.1 und ist genauso bedienbar. In dem Fall mit einem horizontalen Scrolling.

image

Nun hat Microsoft also mit WinJs 3.0 Yet Another JavaScript Library für mobile Clients. Stellt sich die Frage: wer braucht das, wer wird das nutzen? Konzeptionell ist die Architektur gut gelungen und einfach nutzbar. Die für JavaScript typischen Probleme mit Asynchronität finden sich auch hier. Hervorzuheben ist, dass der HTML Code und vermutlich weitestgehend der JS Code aus einer nativen Windows 8.1 Anwendung übernommen werden können. Die HTML5 Designer aus Blend sind das Beste, was der Markt aktuell an WYSIWYG zum Thema zu bieten hat. In der Gesamtbetrachtung bin ich vorsichtig optimistisch, würde aber aktuell kein Projekt damit starten.

Mehr zum Thema Cross Plattform auf der ADCX in Mannheim und Wien.

Training, Schulung, JavaScript, HTML, CSS, Dot Net, Asp Net

Month List