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 />
Kommentare sind geschlossen