Check All Checkboxen mit AngularJS

Wieder einmal komme ich von einer Angular.JS Schulung nach Hause. Wieder einmal hatten die Teilnehmer des Trainings durchaus Probleme die Konzepte von Angular mit ihren bestehenden Skills zu verbinden. Konkret ging es um den Anwendungsfall eine Liste von HTML Checkboxen mit einem Klick zu aktivieren. Bei der Rückreise vom Angular Kurs mit der Bahn, hat man Zeit darüber nachzudenken und das Ergebnis ist nicht was man erwarten würde.

Kennen Sie den? Wer einen Hammer besitzt, für den ist das Leben voller Nägel. Kann man so interpretieren, das man das tut was man kann. In einer Angular Single Page Application ist das ein HTML View und ein Viewmodel Scope per JavaScript Controller. Dieser stellt die Methoden bereit um aus dem View Änderungen am Scope bla bla bla.

Schon sind wir in ein kleines Buzz Word Bingo mit Angular eingestiegen. Also lassen sie uns einen Schritt zurück treten. Worum geht es eigentlich in einer Web Site? Das völlig konstruierte Beispiel hat folgende Benutzer Anforderungen.

  • Eine Liste Namen anzeigen
  • max 2 Namen einzeln auswählen
  • die ausgewählten Namen zählen und anzeigen
  • Alle Namen aus oder abwählen können

Mancher JavaScript Developer fängt nun an Tests zu schreiben, andere einen Controller. Ich bin der visuelle Typ und zeichne das UI zuerst.

image

Der dazu notwendige Angular.JS Controller

   1:   angular.module('App', [])
   2:          .controller('myController', function ($scope) {
   3:              $scope.liste = [{ wert: "hannes", checked: false },
   4:                  { wert: "maria", checked: true },
   5:                  { wert: "franz", checked: false },
   6:                  { wert: "Hias", checked: false }];
   7:          });

 

Nun könnte man in den Controller einen Watcher einbauen, der die Liste überwacht und bei Änderung einen Zähler anwirft. Der Wert wird dann einer Scope Variablen zugewiesen. Noch wach?

Warum muss sich eine User Interface Geschichte im Controller abspielen? Wo steht das geschrieben?

Angular ist relativ mächtig, wenn es um Datenbindung per Expressions geht. Ausgehend von der Liste der Namen wird mit einem Filter Ausdruck nur die ausgewählten selektiert. Einmal Klammer außen rum und mit lengt die Anzahl ausgeben.

   1:  gesamt {{(liste | filter:{'checked':true}).length }} 

 

Das Ergebnis sieht man am obigen Bild. Well die Grundidee durchaus bestechend ist, wird das gleiche Konzept verwendet um bei mehr als einem ausgewählten Namen die übrigen zu deaktivieren. So kann der Benutzer maximal zwei Namen auswählen, aber die gewählten auch wieder deaktivieren.

   1:  <x ng-repeat="mensch in liste">
   2:     <input type="checkbox" name="test" class="chklist"
   3:         ng-model="mensch.checked"  
   4:         ng-disabled="mensch.checked==false && 
(liste | filter:{'checked':true}).length>1"
   5:     />{{mensch.wert}}
   6:  </x>

Auch das kann man im Screenshot oben erkennen, hannes und hias sind graue Boxen.

Ausgerüstet mit der Erkenntnis, das nicht alles Angular Code Behind sein muss, nun die letzte Aufgabe. Wie kann man alle Checkbocen anhaken oder eben nicht. Wenn man im UI bleibt, ist JQuery das beste Tool um  DOM Manipulationen vorzunehmen. Dafür gibt es verschiedene Selektoren, über ID oder Typ oder eben CSS Klassen, hier chklist genannt.

   1:<input type="checkbox" onclick=" $('.chklist').prop('checked', this.checked)" />Alle<br />

 

Cool oder? Alternative Lösungen die im Web diskutiert (oder gebloggt ) werden kommen auf ein vielfaches an Code.

Eine kleine Randnotiz. JavaScript’s fähigkeiten Objekte zu jeder Zeit zu erzeugen wird mit Angular.js noch magischer. Obiges Beispiel funktioniert auch ohne die checked Eigenschaft der Liste.

   1:   $scope.liste = [{ wert: "hannes" },
   2:                  { wert: "maria" },
   3:                  { wert: "franz" },
   4:                  { wert: "Hias"}];

AngularJS, q$, Learning Partner, REST Api Design und noch vieles mehr

Nachdem Microsoft mit Hinweis auf Datenschutz keine Liste der Microsoft Learning Partner herausgibt, musste ich mir einen anderen Weg suchen. Das schöne daran ist, das viel zu lernen gab, um an die Lösung zu kommen. Wieder einmal.

microsoftgoldlearningpartner

Nur Microsoft Learning Partner dürfen offizielle Trainings zu Microsoft Produkten durchführen. ppedv ist neben aktuell rund 50 anderen, eines dieser zertifizierten Unternehmen in Deutschland. In der höchsten Stufe Gold sind es mit uns weniger als ein Dutzend. Es gibt eine Reihe von Bedingungen und Voraussetzungen, wie Qualität, zertifizierte Mitarbeiter und auch Geld. Als Gegenleistung gibt es die notwendigen Lizenzen für die Nutzung der Software. Auch wenn andere Anbieter Formulierungen verwenden wie “wir verzichten bewusst auf den Einsatz von Herstellerunterlagen” begehen diese eine Lizenzverletzung. Kurz gesagt ist es illegal mit Microsoft Produkten (auch SAP und co) Kundenschulungen durchzuführen.

Wenn Sie sich für Anbieter interessieren die Training durchführen, ohne das Verbrechen zu fördern, hilft die Website Microsoft Pinpoint. Man sucht nach Unternehmen, die in der Kompetenz Learning zertifiziert sind und landet dann auf folgender URL https://pinpoint.microsoft.com/de-DE/browse/companies?competency=100008&page=0

Aus natürlicher Neugierde lasse ich gerne auch mal Fiddler laufen um die Programmierung einer Website kennen zu lernen. Diese Seite nutzt das SPA JavaScript Framework Knockout und umfangreiche REST APi Calls. In Summe weit über 200 Requests. Entsprechend lange dauert der Seitenaufbau.

Das Design der  REST API folgt einem Ansatz, der pro Ressource oder Entität einen Call ausführt. Unter anderem für alle Learning Partner um eine Liste der ID’s zu bekommen oder die Kategorien.

image

Die erste HTTP Endpunkt ist folglich

https://api.pinpoint.microsoft.com/api/v2/de-DE/search/companies/?top=80&competency=100008&orderType=Relevance&sortDirection=1

Da hier offensichtlich ein Odata  Dialekt als Query Syntax verwendet wird, ist es ein leichtes, die Anzahl der Records von 20 auf 80 zu erhöhen.

Für die Firmendaten werden dann je Eintrag in der Liste mindestens zwei weitere REST Calls abgesetzt. Die Partner ID wird in der URL in () übergeben.

api/de-DE/companies(4295952006)/overviews

Durch ergänzen der Rest Methode –zb  Overviews – an die URL wird dann zur passenden Controller -Methode umgeleitet.

image

Rein aus API Gesichtspunkten sehr sauber definierte Schnittstellen. Wer Wert auf reduzierten Traffic legt und einfache Client Implementierung wird die Hände über den Kopf zusammen schlagen. Das fühlt sich an wie select * auf alle Tabellen und Client seitige Joins.

Dabei hat die so auftretende Problemstellung seinen Reiz. Zuerst kommt einen Liste von Partner Id’s, die dann asynchron mit merkbarer Verzögerung um Detail Daten ergänzt wird. Ein Parade Beispiel für den MVVM Ansatz den Angularjs erlaubt, nein vielmehr voraus setzt.

Mein View in HTML5 iteriert über eine Liste von Partner Objekten in dem ein Firma Objekt enthalten ist.

   1:  <div ng-controller="myController">
   2:          <div ng-show="partner.length==0">
   3:              Daten werden geladen...
   4:          </div>
   5:          <div ng-repeat="firma in partner.results | orderBy: 'firma.legal_name'">
   6:              {{firma.company_id}} {{firma.firma.legal_name}}<hr />
   7:          </div>
   8:          total {{partner.total_results}}
   9:      </div>

 

Damit hält sich der Code völlig an die JSON Struktur aus den beiden REST API aufrufen. Wehe die ändert wer!

image

 

image

Erst erfolgt der AJAX Aufruf der Companies und dann je einer pro Company. Um das besser erklären zu können in umgekehrter Reihenfolge erklärt. Der Aufruf der Firmendetails wird in eine Angular.jS Factory abstrahiert.  Mit getCompany(id) lässt sich dann der JSON Stream der Details auslesen.

   1:  .factory('myService', function ($http) {
   2:                  // /api/de-DE/companies(4295952006)/overviews
   3:        return {
   4:             getCompany: function (id) {
   5:            return $http.get('https://api.pinpoint.microsoft.com//api/de-DE/companies(' + id + ')');
   6:            }
   7:           }
   8:   });

 

AngularJS typisch dient ein Controller und ein Viewmodel. Dieser enthält den Scope, also die Daten, und de Funktionen um den Scope zu füllen. Um einen http AJAX Cal auszuführen wird neben dem $scope auch der $http Service per Dependency Injection als Singelton instanziert. Aber auch der vorhin erstellte Service und $q. Mit $q können asynchrone Funktionsaufrufe ohne komplexe Code Schachtelung realisiert werden.

   1:   angular.module('App', [])
   2:     .controller('myController', function ($scope, $http, $q, myService) {
   3:       $scope.partner = [];
   4:       $http.get('https://api.pinpoint.microsoft.com/api/v2/de-DE/search/companies/
?top=80&competency=100008&orderType=Relevance&sortDirection=1'
)
   5:        .then(function (Response) {

 

Auch $http verwendet intern $q. Damit kann dann wenn fertig die .then JavaScript Logik durchlaufen werden. Die Json Daten werden automatisch in ein JavaScript Objekt  geparst (hier Response genannt)).

image

Die Daten werden dann in Zeile 2 einem Scope Objekt zugewiesen und stehen damit für Bindung in der Form partner.total_results (siehe HTML view) bereit.

Als nächstes bekommt Q den Auftrag für alle Einträge in results (aus dem JSON modell) einen Aufuf on der Factory Methode getCompany in die Warteschange zu stellen. Map macht für jeden Eintrag des Arrays den Funktionsaufruf.

Wenn das fertig ist (.then Zeile 6) wird der passende Eintrag in der ursprünglichen Liste des Microsoft Partner Arrays (Scope.partner) gesucht und die erhaltenen Firmen Entität einfach angefügt.

   1:   .then(function (Response) {
   2:       $scope.partner = Response.data;
   3:      
   4:       $q.all(Response.data.results.map(function (result) {
   5:            myService.getCompany(result.company_id)
   6:            .then(function (firma) {
   7:   
   8:                  angular.forEach($scope.partner.results, function (value,key) {
   9:                      if (value.company_id == firma.data.id) {
  10:                          value.firma = firma.data;
  11:                       }
  12:             });
  13:      });
  14:   })
  15:  );

 

Man kann beim Aufruf der Web Seite sehr gut den asynchronen Aufbau des Inhaltes verfolgen, vor allem weil alphabetische Sortierung gewählt worden ist. Damit werden die Einträge während nachträglich pro neuem Eintrag in der Liste umsortiert.

image

Im Ergebnis wurde so ein verschachtelter (nested) Rest API Call asynchron durchgeführt und über eine Bindung Logik im View dargestellt. Ich halte die Lösung für wenig gelungen, weil die API ohne Berücksichtigung der geplanten Darstellung entworfen wurde. Der Universelle Ansatz lohnt bestenfalls nur, wenn auch mehrere vorher nicht feststehende UIs gebaut werden.

Editierbares GridView mit AngularJS

In meinem letzten Angular.js Kurs wollte ein Schulungsteilnehmer Daten als Grid darstellen. Natürlich auch editierbar und mit new Funktion, Wie man es unter anderem von ASP.NET Webforms Datagrid gewohnt ist. Da der Kunde natürlich König ist, wird auch dieser Wunsch erfüllt.

Eigentlich habe ich mich von den multifunktionellen Edit, Insert DataGrid Darstellung ala Excel vor langer Zeit verabschiedet. Ich verwende pro Anwendungsfall eine eigene ASPX Seite, einen View wenn man so möchte. Das ist einfacher zu entwickeln, zu stylen und zu verwenden. Dazu lässt sich die Edit Sicht per Deep Link ansteuern.

Nichts desto trotz ist es in AngularJS leicht möglich client seitige Templates in Abhängigkeit eines Modus zu verwendet. Dazu dient das Attribut ng-include, das eine Zeichenkette mit dem Namen des Templates erwartet. Das Beispiel bereitet auch noch Filterung der Daten vor. Ausserdem kann der Benutzer durch Mausclick auf die Tabellen Reihe das Template wechseln.

   1:  <table class="table table-striped table-bordered table-hover">
   2:     <thead>
   3:          <tr>
   4:            <th>Name</th>
   5:            <th>Geburtstag</th>
   6:            <th>Geschenk</th>
   7:           </tr>
   8:     </thead>
   9:   <tbody>
  10:    <tr ng-repeat="name in namen | filter:suchtext" 
ng-include="getTemplate(name)" ng-click="changeTemplate(name)"></tr>
  11:    </tbody>
  12:  </table>

Die HTML Templates werden in einen Script Block eingebettet. Das Script Element wird als Type ngtemplate gekennzeichnet und per ID Attribut mit Namen versehen. Hier item und edit.

Das Item Template dient der Anzeige. Datum wird per Angular Date Filter in ein deutsches Format gebracht. Um bei der Checkbox das auslösen des Template switch zu verhindern, wird das Click Event in Zeile 10 behandelt.

Das Edit Template ist ein wenig komplexer. Die Variable Name wird nur im Viewmodel upgedatet, wenn der Fokus wechselt. Die Anzeige des Datums wird aufwändig mit einer eigenen Direktive formatiert. Um aus dem Edit Modus in den Anzeigemodus zu wechseln, dient letztendlich der unedit Button.

   1:  <script type="text/ng-template" id="item">
   2:  <td>
   3:      {{ name.name}}
   4:  </td>
   5:  <td>
   6:      {{ name.datum | date : "dd.MMMM yyyy" }}
   7:  </td>
   8:   
   9:  <td>
  10:      <input type="checkbox" ng-model="name.checked" 
onclick="event.stopPropagation();">
  11:           
  12:  </td>
  13:  </script>
  14:  <script type="text/ng-template" id="edit">
  15:  <td>
  16:      <input ng-model="name.name" ng-model-options="{ updateOn: 'blur' }" class="form-control" />
  17:  </td>
  18:  <td>
  19:      <input type="text" ng-model="name.datum" 
ng-model-options="{ updateOn: 'blur' }" class="form-control" deutsches-datum/>
  20:     
  21:  </td>
  22:   
  23:  <td>
  24:      <input type="button" value="unedit" class="btn  btn-xs" ng-click="save(name)">
  25:  </td> 
  26:  </script>

 

Der Benutzer kann so eine oder mehrere Zeilen editieren. Dieses Beispiel erlaubt auch das Inplace einfügen eines neuen Datensatzes. Die Checkbox wird ausgewählt, wenn ein Geschenk zum kommenden Geburtstag gekauft wurde.

image

Da AngularJS dem MVVM Pattern folgt, werden Änderungen an den Daten auch adhoc ins ViewModel geschrieben. Speichern ist dementsprechend die falsche Bezeichnung- Das zur Erklärung des Unedit Buttons.

Der REST Service ist mit ASP.NET Api gebaut und liefert ein simples Array.

   1:  Public Function GetValues() As IEnumerable(Of String)
   2:       Return New String() {"hannes", "franz", "Alina", "Melanie", "Miriam", "Matthias", "George"}
   3:  End Function

 

Weil die Single Page Application auch ein wenig Code Eleganz bieten soll, wird der Web Service Aufruf in einen Angular.js Service als Factory gekapselt (das nennt man da eben so).

Um das Array zu einem Objekt Array aufzuwerten, wird in Zeile 9 en zufälliges Datum eingestreut. Der mode dient später um Item:0 oder Edit:1 abzubilden.

   1:  App.factory('namenService', ['$http', function ($http) {
   2:      var myFactory = {};
   3:      var _getNamen = function () {
   4:          return $http.get('http://url/api/hannes').then(function (results) {
   5:              var arr;
   6:              arr = jQuery.map(results.data, function (n,i) {
   7:                  return ({
   8:                      "name": n,
   9:                      "datum": new Date(2015, 2, Math.random() * 365),
  10:                      "mode": 0
  11:                  });
  12:   
  13:              });
  14:              return arr;
  15:          });
  16:      };
  17:      myFactory.getNamen = _getNamen;
  18:      return myFactory;
  19:  }]);

Im Angular Controller wird der Service per Dependency Injection instanziert und dann in Ziele 3 die Methode aufgerufen.

Auch die Methode um einen neuen Datensatz anzuhängen, wird per Add angelegt.

Mittelmäßig spannend ist die Logik für den Template anzeige in Zeile 15, die aus 0 und 1 der mode Eigenschaft einen String erzeugt. Die Save Methode muss nur noch den Mode Wert ändern.

   1:  App.controller('namenController', ['$scope', 'namenService', function ($scope, namenService) {
   2:      $scope.namen = [];
   3:      namenService.getNamen().then(function (results) {
   4:          $scope.namen = results;
   5:      });
   6:      $scope.add = function () {
   7:          $scope.namen.push({"name": "",
   8:              "datum": "",
   9:              "mode": 1
  10:          });
  11:      };
  12:      $scope.save = function (name) {
  13:          name.mode = 0;
  14:      };
  15:      $scope.getTemplate = function (name) {
  16:          if (name.mode ==0) return 'item';
  17:          else return 'edit';
  18:      };
  19:      $scope.changeTemplate = function (name) {
  20:          name.mode = 1;
  21:      };
  22:  }]);

Für den AngularJS Experten sollte der Rest klar sein. Für alle anderen der HTML5 Teil, der die Buttons darstellt. So nebenbei mit Bootstrap als UX Framework.

   1:  <input ng-model="suchtext" placeholder="searchtext" class="form-control"
   2:                      ng-model-options="{ updateOn: 'blur' }"/>
   3:  <a  class="btn btn-default">suche</a><br />
   4:  <a ng-click="add()" class="btn btn-default">add</a><br />

ASP.NET Web API 302

Nach dem einfügen eines Authorize Methoden Attributes vor eine ASP.NET Web API Controller Methode, kann nur mehr ein angemeldeter Benutzer diese nutzen.  ASP.NET MVC und ASP.NET Web API müssen an dieser Stelle zwei unterschiedliche Anwendungsfälle abdecken. Bei einer UI gesteuerten Anwendung wird der Benutzer bei Aufruf einer geschützten Seite zur Login Seite umgeleitet. Bei einem Service soll eine 401 HTTP Meldung gesendet werden.

Die Implementierung des AuthorizeAttribut Klasse fand sich in der Vergangenheit in zwei Unterschiedlichen Assemblies. System.Web.MVC und System.Web.Http. Etwas was in vielen Foren Einträgen immer wieder diskutiert wird.

Allerdings reagiert ein von mir erstellte REST Service anders wie erwartet. Es wurde mit Visual Studio 2013 ein Web Projekt angelegt und Web API und Web Forms angehakt.

Ein Testservice liefert nun eine HTTP 302 Meldung statt der erwarteten 401. Nur aus  dem HTML Body Text erkennt man das Problem {"Message":"Authorization has been denied for this request."}

image

Das ist für eine Auswertung des AJAX Request per JavaScript Framework (zb AngularJS) wenig geeignet.

Die Ursache liegt in der Verwendung der OWIN Klassen. Mehr unabsichtlich als wissentlich. Jedenfalls kann man das Verhalten in der Datei Startup.Auth.vb erkennen und auch verändern.  Es wird Zeile 7-10 eingefügt. Damit wird geprüft ob es sich um einen REST API Aufruf handelt. Nur wenn nicht, wird der Redirect auf die Login Seite durchgeführt.

   1:  Public Sub ConfigureAuth(app As IAppBuilder)
   2:  ....
   3: ' Enable the application to use a cookie to store information for the signed in user
   4:   app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
   5:     .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
   6:      .Provider = New CookieAuthenticationProvider() With {
   7:          .OnApplyRedirect = Sub(x)
   8:                          If x.Request.Uri.AbsoluteUri.Contains("/api/") = False Then
   9:                             x.Response.Redirect(x.RedirectUri)
  10:                          End If
  11:                   End Sub,

 

Der HTTP Request in Fiddler dargestellt zeigt nun das erwartete Ergebnis eine 401 unauthorized Meldung.

image

Obiger Code ist rein konzeptionell zu verstehen und deckt sicher nicht alle Anwendungsfälle ab.

30.Februar 2015

Wer glaubt, das der Februar maximal 29 Tage haben kann, soll sich mal in JavaScript und HTML Tiefen stürzen. Datum und Uhrzeit ist in der IT Welt schon immer keine leichte Aufgaben gewesen. Wer lange genug dabei ist, wird sich noch den den Millenium Bug erinnern. Aber auch heute ist es nicht trivial mit Zeitzonen und Daylight Saving Time zu operieren. Am Ende dieses Artikels wird der  Leser den Glauben an die Programmierwelt verloren haben.

In HTML5 werden Input Elemente mit neuen Typ Attributen versehen. Auf den ersten Blick würde man meinen, das damit formatierte Datumseingabe möglich ist. Mitnichten, nicht nur der Internet Explorer 11, sondern auch nahezu die restliche Browserwelt unterstützt zwar das Attribut, beachtet es aber weiter nicht. Der Benutzer kann also in folgendes HTML Schnippsel auch Text eingeben,

   1:   <input type="date" id="d1" /> 
   2:   <a onclick="alert(document.getElementById('d1').value);">wert</a>

Ein richtiger Datumstyp muss auch die Lokalisierungsdaten beinhalten. Die ISO-8601 definiert das Format bis hin zur Zeitzone. AngularJS setzt auf das HTML5 und damit auf das Eingabeformat- total Benutzerfreundlich.

image

Der Screenshot stammt aus folgendem AngularJS Beispiel

   1:  <input ng-init ="dat='2015-02-14T11:24:05.0Z'" ng-model="dat"/><br />
   2:  {{dat | date: "dd.MM.yyyy"}}<br />

Die Anzeige wird mit dem Angular Date Filter formatiert, so das sie dem typische deutschen Datumsformat entspricht.

Die Bindung eines Date INPUT ist auf der Angular Website samt live Beispiel beschrieben. Wenn sich ein Benutzer an das ISO Format Jahr-Monat-Tag hält, dann kann er Problemlos den 30.Februar als gültiges Datum eingeben.

image

Weder das eine noch das andere ist in unserem Kulturkreis geläufig. Die Anzeige lässt sich passend formatieren. Die Eingabe leider nicht.

AngularJS typisch wird für Manipulation eines HTML DOM Elements eine Direktive erzeugt. Wer mit Silverlight oder WPF gearbeitet hat, kennt die Value Converter Klassen, die man zwischen bindendes UI Element und der Viewmodel Eigenschaft hängt. Quasi das selbe lässt sich mit einer Angular Direktive erreichen.

Die Methoden $parsers und $formatters entsprechen den Convert und ConvertBack Methoden.

   1:  .directive('deutschesDatum', function ($filter) {
   2:      return {
   3:       require: 'ngModel',
   4:       link: function (scope, element, attrs, ctrl) {
   5:           ctrl.$parsers.push(function (data) {
   6:                       //View -> Model
   7:                var d = data.split(".");
   8:                var parsedDate = new Date(d[2], d[1] - 1, d[0]);
   9:                return parsedDate;
  10:                });
  11:          ctrl.$formatters.push(function (data) {
  12:                      //Model -> View
  13:               return $filter('date')(data, "dd.MM.yyyy");
  14:           });
  15:       }
  16:  };

 

Obiger JavaScript Code löst aber noch nicht das Problem des 30.Februars. Man müsste mehr Code in den Teil ab Zeile 5 schreiben. Es reicht das Konzept.

In der HTML5 Deklaration wird nun die Direktive als Attribut eingefügt.

   1:   <input id="datum1" type="text" ng-model="datum" deutsches-datum /><br />

 

image

Üblicherweise werden für Datumseingabe fertige Kalender Popup verwendet.

Webforms schlägt Angular.js

Referenten bemühten in der Vergangenheit gerne Google Trends um den Siegeszug von Angular zu visualisieren. Wie immer bei Charts, geht es aber plötzlich auch mal nach unten und dann ist im Börsen Jargon abstoßen angesagt. Ein kleiner völlig sinnfreier Diskurs durch Trendcharts.

Folgender Chart wurde von Wintellect entliehen und zeigt eindrucksvoll wo die Reise hingehen wird.

angulartrends_thumb[1]

Zum Vergleich der Goldpreis- kaufen – kaufen - kaufen

image

Wer am 17.September 2012 ausgestiegen ist, konnte sich freuen. Jedenfalls ungefähr 40% mehr freuen, als ein Jahr später.

Die Gurus sind sich einig: “Angular.js will rock the world” und man sollte quasi jedwelche andere Tätigkeit, bis hin zum täglichen Toilettengang einsparen, zugunsten eines neuen Controllers oder Direktive.

Was sagt nun Google Trends zu diesem Trend (oh - eine Rekursion). Upps- die Angular.js Aktie gerät weltweit unter starken Verkaufsdruck.

image

Seit September 2014 geht es mit den Suchanfragen steil bergab. Woran liegt das? These #1 – alle können nun Angular.js programmieren und suchen nicht mehr danach. These #2: sie suchen nach was anderen und meinen Angular. These #3 das Interesse schwindet.

Nun kann man die Aussagekraft steigern, indem man die Benutzer Suchen in Relation stellt, z.B. einem anderen führenden JavaScript Framework: Jquery- Eindeutig, mit Jquery geht es auch steil bergab (nicht ganz so steil). Ähmm – wo ist Angular?

image

Der Unterschied liegt in der Dimension 1:100. Allerdings lassen sich Statistiken, wie aus dem Gold Beispiel gelernt, durch geschickte Wahl des Zeitausschnitts oder der Skalierung in der Grundaussage ins Gegenteil verkehren. Wollen wir mal sehen ob ich Angular nicht doch noch die kommende Weltherrschaft andichten kann.

Erster Versuch Zeitraum – die letzten 12 Monate. Da muss was gehen.

image

Ja – sieht ja schon ganz gut aus- Weihnachten haben die Leute sich ihren Urlaub gegönnt und nicht programmiert.

Um die Validität der Daten zu prüfen, noch ein Vergleichswert. Ich wähle die uralte und generell total verpönte Webforms Technologie.

image

Smileyes kann nicht sein, was nicht sein darf.

[Update]

Erwartungsgemäß hat die Community den Ball aufgenommen.

Thomas Bandt empfiehlt React.js zum Kauf https://www.google.de/trends/explore#q=react.js

Marcel Spiess liefert den Beweis für These #2 https://www.google.de/trends/explore#q=angularjs&cmpt=q&tz=

Angular Listen animiert filtern

Zur Natur einer Single Page Web Applikation gehört es, Daten im Webbrowser zu sortieren oder zu filtern. Das SPA Framework AngularJS bietet zu diesem Zweck vordefinierte Filter. Zusätzlich können die Listenelement sehr einfach animiert werden, wenn ein Eintrag aus verschwindet. Dabei überrascht der gewählte Technologie Ansatz der Google Framework Entwickler durchaus.

animateIm ersten Schritt wird ein sehr einfach gehaltenes UI per HTML deklariert. Eine Liste von PKW in der gefiltert werden kann. Per Bootstrap ein wenig aufgehübscht. Um das Beispiel klein zu halten, wird auf den Angular typischen Controller verzichtet und ein Array im HTML Code per ng-init deklariert. Eigentlich ein MVx Affront.

Die Liste wird im View per ng-repeat durchlaufen. Dabei wird der Wert suchtext aus dem Input Feld, gebunden und als Filter verwendet.

Dieses Angular Beispiel stammt aus meinem Angular Training.

 

   1:  <body ng-app="App"
   2:        ng-init="marken=['Audi', 'Bmw','Chevrolet', 'Chrylser',
'Fiat', 'Ford', 'Honda', 'Mercedes', 'Opel', 'Range Rover'];"
>
   3:      <div class="well" style="margin-top: 30px; 
margin-left: 30px; width: 150px; overflow: hidden;"
>
   4:          <div class="input-group">
   5:              <input type="text" class="form-control" 
placeholder="Filter" ng-model="suchtext">
   6:              <span class="input-group-btn">
   7:                  <button class="btn btn-default" type="button">
   8:                      <span class="glyphicon glyphicon-search"></span>
   9:                  </button>
  10:              </span>
  11:          </div>
  12:   
  13:          <ul class="nav nav-pills nav-stacked">
  14:              <li class="list-item" 
ng-repeat="name in marken | filter:suchtext">
  15:                  {{name}}
  16:              </li>
  17:          </ul>
  18:   
  19:      </div>

Wenn der Benutzer einen Buchstaben tippt, soll die Ergebnismenge in der Liste geringer werden. Die verbleibenden Einträge werden dann per Animation zusammen geschoben, Dabei geht Angular.js seit der Version 1.2 einen neuen Weg (der so nebenbei nicht abwärtskompatibel ist).

Das Modul ngAnimate nutzt nun ausschließlich CSS Klassen, die einer bestimmten Konvention folgend deklariert werden müssen. Das ganze erinnert eher an Jquery Mobile als an Angular. Im vorigen HTML Beispiel wurde dafür die CSS class liste-item definiert (Zeile 14). Dieser Name ist völlig frei wählbar.

Damit die Angular Animationen überhaupt nutzbar sind, braucht es zwei Dinge. Eine Referenz auf die JavaScript Datei angular-animate.js und eine Injection des Services ins Modul. Um es in der nicht Angular Sprache auszudrücken, der App muss die Animation Library bekannt gemacht werden,

   1:     angular.module('App', ['ngAnimate'])
   2:     

Nun fehlen nur noch die CSS Klassen. Diese müssen der Namenskonvention folgend erstellt werden. Prefix ist der Name des CSS list-item aus Zeile 14. Gefolgt von der Funktion wie ng-leave oder ng-enter. Je nachdem welches Angular gebundes DOM Element animiert  werden soll, stehen unterschiedliche Animationen bereit. Hier eben leave, enter und auch noch move

   1:  .list-item.ng-enter,
   2:  .list-item.ng-leave {
   3:  -webkit-transition: all linear 0.5s;
   4:  transition: all linear 0.5s;
   5:  }
   6:   
   7:  .list-item.ng-leave.ng-leave-active,
   8:  .list-item.ng-enter {
   9:  opacity: 0;
  10:  max-height: 0;
  11:  }
  12:   
  13:  .list-item.ng-leave,
  14:  .list-item.ng-enter.ng-enter-active {
  15:      opacity: 1;
  16:      max-height: 30px;
  17:  }

Damit ist die Aufgabe auch schon erledigt. Positiv daran ist, das nachträglich durch den Designer Animationen hinzugefügt werden können. Allerdings ist der Lösungsweg wenig intuitiv, selbst für den Angular Experten. Wenig begeistert bin ich von dem Design Bruch zu älteren Versionen. So findet man häufig nicht funktionierende Beispiele in diversen Blog Einträgen.

End 2 End Test the Visual Studio Way

Auch wenn die ganze Welt von JavaScript besetzt ist, ein kleines Dorf an der Grenze zu Österreich leistet unerbittlich Widerstand. Test einer Web Anwendung muss doch auch mit Visual Studio 2013 funktionieren. Und das geht, ganz ohne jeden Code.

Ausgehend von einem einfachen Web Projekt, wird ein Taschenrechner implementiert. Zwei Felder, ein Add Button, am Client per JavaScript gerechnet. Dabei spielt das keine Rolle ob ASP.NET Webforms, SPA mit Angular oder MVC.

Zuerst wird dem Web Projekt ein Coded UI Test Projekt in der Visual Studio Solution hinzugefügt. Im neuen Projekt wird per Add Item ein Coded UI Test erstellt.

image

Ziel ist es das Nutzerverhalten im User Interface zu simulieren. Dafür werden die Eingaben aufgezeichnet,

image

Nun erscheint rechts unten der Testrecorder –genannt UI Map Test Builder. Dieser zeichnet alles auf, was wir so tun. Egal mit welcher Anwendung.

image

Die Aufnahme startet mit dem roten Button. Danach öffnet man den Browser und ruft die URL auf. Falls man Aktivitäten später entfernen möchte, kann man dies über das Treppensymbol. Bei klick zeigt es die Liste der recorded Actions an.  Versehentlich erzeugte Einträge lassen sich später so noch löschen. Im Brweoser wurden in die HTML Input Elemente die Zahlen 1 und 2 eingetippt und am Schluss der Berechnen Button geklickt.

image

Nun wird Generate Code gedrückt. Allerdings fehlt uns noch die Prüfung des Rechenergebnisses. Kurz gesagt ob 1+2 auch 3 ergibt.

image

Jetzt sollte eine Art Zielscheibe im Coded UI Test Builder sichtbar sein

image

Dieser wird angeklickt und dann der Mauszeiger auf dem DIV im Browser Fenster platziert, das das Ergebnis enthält (also 3). Dann wird STRG SHIFT I gedrückt.

Es wird einen Eigenschaftsdialog des DIV (hier mit der ID c) angezeigt. Dann scrollt man auf die Eigenschaft innerText und wählt eine Assertion.

codedUI2

Diese Bedingung definiert den Erfolg oder Misserfolg des Tests. Hier soll das Ergebnis 3 lauten. Der Comparator AreEqual wird mit dem Comparison Value 3 belegt.  Anschließend muss nochmalig die Code Generierung angestoßen werden.

In meinem Projekt befinden sich auch Unit Testroutinen, die mit Jasmine.js erstellt wurden. Außerdem ein nicht funktionierender E2E Test mit protractor.js,. CalcUitestAdd ist mein aufgezeichneter Coded UI Test.

Alle Tests werden im Test Explorer angezeigt und durchgeführt.

image

Wenn der Test läuft, startet einen Instanz des Internet Explorers und wird wie von Geisterhand mit Daten gefüllt. Auch der Button wird geklickt, dadurch die Berechnung angestoßen und das Ergebnis im DOM Element mit der ID c angezeigt. Ein übereifriger HTML Designer (rein fiktiv) hat aus dem DIV ein Label gemacht und der Test schlägt fehl.

image

Dabei funktioniert die Webseite trotzdem korrekt. Das ist genau das Problem von Tests, egal ob Unit Test oder E2E, auch Tests haben Fehler. Das bedeutet ein Test schlägt fehl, obwohl der Code korrekt ist, oder noch schlimmer umgekehrt.

Testen von Web Apps

Im Grunde nach werden zwei Arten von Tests durchgeführt mit dem Ziel Software Qualität zu steigern. Der Unit Test prüft mehr oder weniger granular Methoden auf erwartete Rückgabe Werte. Der End 2 End Test nimmt die komplette Anwendung und imitiert die normale Nutzung übers UI.

In meinem aktuellen Umfeld Angular.js wird Jasmine.js als Testframework hauptsächlich eingesetzt. Um besagte End 2 End (E2E) Integration Tests zu erstellen, verwendet die JavaScript Community protactor.js. Das wiederrum setzt node.js als Laufzeitumgebung voraus.

Um einen Taschenrechner zu testen könnte man folgenden Test verwenden

   1:  describe('Webpage End 2 End', function () {
   2:      beforeEach(function () {
   3:       });
   4:      it('Rechnen 1+2 ', function () {
   5:          browser.get('calc.html');
   6:          element(by.id("a")).sendKeys(1);
   7:          element(by.id("b")).sendKeys(2);
   8:          element(by.id('btn')).click();
   9:          var c = element(by.id("c")).getAttribute('value');
  10:          expect(c).toEqual(3);
  11:      });
  12:  });

Um ehrlich zu sein, dieser Test, wird bei mir aufgrund fehlender Infrastruktur aktuell im Visual Studio 2013 Projekt ignoriert.

image

Nachdem ich einen Schritt zurück getreten bin, stellte sich mir die Frage, warum muss die Testlogik eines HTML UI Tests mit JS geschrieben werden? Richtig – dafür gibt's keinen Grund. Es geht auch mit .net.

Und irgendwer hat sich auch die Zeit genommen eine Portierung nach .net vorzunehmen https://github.com/bbaia/protractor-net

Im einem Anfall von Faulheit, habe ich per google ein Stück Beispiel Code gesucht und von Dan Wahlin gefunden https://github.com/DanWahlin/CustomerManagerStandard indem die Library auch verwendet wird.

Jetzt lehne ich mich noch weiter zurück und erinnere mich an die guten alten Lasttests oder auch Stresstest genannt. Vor vielen Jahren gab es schon mal das Web Application Stress Test Tool von Microsoft (wcat). Damit wurde das Verhalten des Benutzers im Browser aufgezeichnet und dann immer wieder automatisch ausgeführt um die Anzahl der gleichzeitigen Zugriffe zum Webserver bzw. Website messen zu können.

Fazit: Es  muss eben nicht immer JavaScript sein. Außerdem halte ich es für völlig ausgeschlossen einen UI Test bzw. Integrations Test dem TDD Pattern folgend zuerst zu implementieren. Alleine schon weil TDD nur mit gemockten (also simulierten) Datenbank zugriffen arbeiten soll.

Meine nächste Aufgabe wird sein sich die Funktion Web Stress Test, das Recording über den Internet Explorer und die Coded UI Tests von Visual Studio 2013 zu beschreiben.

Visual Studio und Jasmine

Um ganz ehrlich zu sein, um TDD (Testdriven Development) habe ich bisher einen großen Bogen gemacht. Die theoretischen Grundlagen, ein zwei Beispiele und fertig. Der Aufwand ist definitiv höher und der erwartete Gewinn kaum erkennbar, da meine Software ohnehin nie Fehler hat.

Nun ist es aber so, dass JavaScript auf absehbare Zeit, die gesetzte Sprache ist. Anders als bei meinem geliebten VB.NET, bügelt kein Intellisense und Compiler meine Typos aus. JavaScript ist eben sehr dynamisch, auch in den Ergebnissen. Nach einem intensiven Coding Dojo, reifte in mir die Erkenntnis, das Tests ein adäquates Mittel sind, um in JavaScript Projekten reproduzierbare Ergebnisse zu erhalten.

Um nicht gänzlich die geliebte Heimaterde zu verlassen, habe ich mir in den Kopf gesetzt Node.js, npm und anderes Gedöns dabei links liegen zu lassen und ausschließlich Visual Studio zu verwenden.

In der Regel brauchen Units Tests ein Framework das hilft, die Tests zu schreiben und einen sogenannten Runner, der die Tests zu einem bestimmten Zeitpunkt ausführt. Für letzteres wird gerne Karama verwendet, dass auf Node aufsetzt. Für das Test Szenario wird häufig Jasmine genutzt.

Aufsetzen der Testumgebung

Im ersten Schritt wird Visual Studio 2013 um zwei Extensions erweitert, die beide Chutzpah genannt werden (idealer Name für Google suche). Das eine  ein Testrunner der das Context Menü von Visual Studio erweitert. Das zweite als Adapter für die Integration der Tests in den Visual Studio Unit Test Explorer.

https://visualstudiogallery.msdn.microsoft.com/71a4e9bd-f660-448f-bd92-f5a65d39b7f0

https://visualstudiogallery.msdn.microsoft.com/f8741f04-bae4-4900-81c7-7c9bfb9ed1fe

Dies muss nur einmalig installiert werden.

Im nächsten Schritt wird ein Web Projekt auf ASP.NET MVC Basis (Sprache C#) angelegt. Per Nuget Paket Manager wird das Paket JasmineTest in das Projekt eingefügt.

Jasmintest

Damit hat man ein fertiges Beispielprojekt und die passenden Tests dazu. Als Spielwiese vorerst ausreichend. Die JavaScript Logik liegt in den Beispiel Dateien Player und  Song. Die Tests in PlayerSpec und in der Helper Datei.

image

Ein Jasmine Test beginnt mit einem Beschreibungsblock (Describe). In den Zeilen 2-8 wird vorbereitender Code ausgeführt. Mit dem IT Kommando wird der Test eingeleitet und damit die eigentliche Logik ausgeführt. Expect beschreibt das erwartete Ergebnis.

   1:  describe("Player", function () {
   2:    var player;
   3:    var song;
   4:   
   5:    beforeEach(function() {
   6:      player = new Player();
   7:      song = new Song();
   8:    });
   9:   
  10:    it("should be able to play a Song", function() {
  11:      player.play(song);
  12:      expect(player.currentlyPlayingSong).toEqual(song);
  13:   
  14:      //demonstrates use of custom matcher
  15:      expect(player).toBePlaying(song);
  16:    });

 

Es gibt verschiedene Arten den Test auszuführen. Wenn man das Jasmine.js Framework direkt von Github lädt, befindet sich im Paket eine Datei SpecRunner.html. Die kann man im Browser aufrufen, ganz ohne Visual Studio oder Webserver. Im Nuget HTML Template wird diese Datei als Razor View ausgeführt, samt Controller. Also Server rendert Code. Eigentlich völlig unnötig, außer man möchte die Routing Engine von MVC unbedingt nutzen. Der JasmineController lädt den SpecRunner.cshtml.

   1:    public class JasmineController : Controller
   2:      {
   3:          public ViewResult Run()
   4:          {
   5:              return View("SpecRunner");
   6:          }
   7:          
   8:      }

Im Browser führt die URL /Jasmin/run folglich zum Test, der hier in fünf Stufen erfolgreich ist.

image

Eine weitere Möglichkeit die Tests laufen zu lassen findet sich im Kontext Menü des Projektes.

image

Die Ergebnisse lassen sich im Output Fenster von Visual Studio analysieren. Allerdings benötigt Chutzpah verweise auf die Code Dateien in der test Datei. Beim ersten Beispiel mit der HTML Datei im Browser waren das simple Script Referenzen. Im Falle von Chutzpah werden speziell formatierte Kommentare am Anfang eingefügt, die vom Testrunner geparst werden.

   1:  /// <reference path="C:\Users\user\Documents\visual 
studio 2013\Projects\WebApplication7Test\WebApplication7Test\
Scripts\jasmine-samples\player.js"
/>
   2:  /// <reference path="C:\Users\user\Documents\visual 
studio 2013\Projects\WebApplication7Test\WebApplication7Test\
Scripts\jasmine-samples\song.js"
/>
   3:  /// <reference path="C:\Users\user\Documents\visual 
studio 2013\Projects\WebApplication7Test\WebApplication7Test\
Scripts\jasmine-samples\spechelper.js"
/>

Weil die Test Runner Extension installiert ist, werden die Tests auch automatisch beim Build des JavaScript Projektes im Test Explorer durchgeführt.

image

Das ist für mich der eigentliche Ersatz für den Compiler.

Für einen ersten Versuch wurde die Logik des Players verändert und der Status isPlaying bei Play auf false gesetzt. Prompt schlagen zwei Tests fehl.

image

Als nächstes wird die Verwendung anhand eines Angular.JS Controllers demonstriert, da dies auch Bestandteil der Angular Schulung ist.

Training, Schulung, März Aktion

Month List