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"}];

mit TypeScript strukturierten CSOM Code schreiben

TypeScript bietet Möglichkeiten um JavaScript Code in Klassen und Module strukturieren. Natürlich kann eine SharePoint Hosted App in SharePoint 2013 auch mit TypeScript erstellt werden (siehe Blog)

Ich habe mir mal Gedanken gemacht, wie ich die üblichen CSOM Aufrufe in Klassen verpacken kann um das ganze etwas strukturierter im Code darzustellen. Ein üblicher CSOM Aufruf kann so aussehen:

1 var ctx: SP.ClientContext = SP.ClientContext.get_current(); 2 var user: SP.User = ctx.get_web().get_currentUser(); 3 ctx.load(user, 'Title'); 4 ctx.executeQueryAsync( 5 () => { 6 $('#message').text(user.get_title()); 7 }, 8 () => { });

 

Üblicherweise benötigt man den ClientContext, führt einzelne Arbeitsschritte am Context aus und wartet dann auf den Rückruf der executeQueryAsync-Methode. Meistens lasse ich mir im Failure-Callback noch die Fehlermeldung ausgeben.

Meine Idee ist nun, diese wiederkehrenden Schritte in eine Basis-Klasse zu verpacken und die konkreten Aufgaben in abgeleitete Klassen zu schreiben. Ich definiere somit die Klasse “Job”:

1 class job { 2 protected ctx: SP.ClientContext = SP.ClientContext.get_current(); 3 public success: (sender : any, args: SP.ClientRequestSucceededEventArgs) => void; 4 5 public errorCallback: (sender: any, args: SP.ClientRequestFailedEventArgs) => any = 6 (sender, args) => { 7 alert(args.get_message()); 8 }; 9 10 doIt() { 11 this.work(); 12 this.ctx.executeQueryAsync(this.success,this.errorCallback); 13 } 14 15 work() { 16 } 17 }

 

In der Klasse werden die beiden Rückruf-Funktionen als Member der Klasse definiert. Wir finden success und errorCallback. Die errorCallback-Methode wird gleich mal mit einer Alert-Box versehen die die Fehlermeldung ausgibt. Die Success Methode bleibt zunächst leer.

Mein erster Versuch sah vor, diese Methoden in den abgeleiteten Klassen zu überschreiben. Leider funktioniert das nicht, da wie in JavaScript üblich, in der CallBack Methode “this” nicht auf die Klasseninstanz zeigt. (Spaß mit this) Daher der Weg, die Methode als Properties zu definieren und später im Constructor die Methoden zuweisen.

Wenn ich nun das Beispiel von oben mit meinem Job programmieren möchte, so ist eine abgeleitete Klasse “getUserNameJob” zu erstellen. In dieser Klasse muss im Constructor die “Work” und die “Success” Methode definiert werden. Um das Resultat an den Aufrufer zurück zu geben wird ein “jobCallback” definiert der dann vom Aufrufer zu übergeben ist.

1 class getUserNameJob extends job { 2 public jobCallback: (userName: string) => any; 3 4 private user: SP.User; 5 6 constructor() { 7 super(); 8 9 this.work = () => { 10 this.user =this.ctx.get_web().get_currentUser(); 11 this.ctx.load(this.user, 'Title'); 12 }; 13 14 this.success = (sender, args) => { 15 var str = this.user.get_title(); 16 this.jobCallback(this.user.get_title()); 17 } 18 } 19 }

Um diesen Job nun zu verwenden wird eine Instanz gebildet, dem Callback eine Methode zugewiesen und die “doIt()” Methode aufgerufen:

1 var j: getUserNameJob = new getUserNameJob(); 2 j.jobCallback = (userName) => { 3 $('#message').text(userName); 4 }; 5 j.doIt();

Von diesem Basisgerüst ausgehend können nun diverse spezialisierte JobKlassen gebildet werden. Z.B. das Auslesen von ListItems oder auch der Zugriff auf das HostWeb kann so in handliche Basis-Klassen verpackt werden.

Ein detailliertes Demo hierzu zeige ich auf der SharePoint Konferenz in Erding (10.-11.3.2015) in der Session “TypeScript – typisiertes JavaScript”

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.

SCCM 2012 R2: Linux-Systeme hinzufügen

Da ich in der letzten Zeit immer wieder gefragt wurde, wie man einen Linux-Computer zur Verwaltung von SCCM hinzufügen kann, möchte ich dies hier kurz darstellen.

Zunächst benötigt man hierzu die Installationsquellen für den Configuration-Manager-Client für Linux bzw. Unix. Diese lassen sich über den Splash-Screen der SCCM-DVD besorgen:

linux01

Über diesen Link gelangt man (je nach Sprache) auf einer Microsoft-Webseite, z.B. dieser:

http://www.microsoft.com/de-de/download/details.aspx?id=39360

linux02

Beim Klick auf “Herunterladen” steht noch die Wahl, welche Version benötigt wird:

linux03

Die heruntergeladene EXE-Datei muss nun noch mittels Doppelklick entpackt werden:

linux04  linux05

Nach dem Entpacken müssen die Dateien auf das gewünschte Linux-/Unix-System kopiert werden. Beispielhaft verwende ich dazu SSH:

linux06

Nach dem Kopieren der Dateien muss die Datei “install” noch ausführbar gemacht werden. Dies geschieht mittels

chmod +x install bzw. sudo chmod +x install

linux07

Nach diesem Vorgang kann die Installation gestartet werden. Als Parameter sind der Name eines Management-Point-Servers, der Site-Code sowie die notwendige Linux-Version anzugeben:

linux08

In meinem Fall lautet der Aufruf:

./install –mp srv1.kurs.intern –sitecode PS0 ccm-Universalx64.tar

Sobald die Installation komplett ist, muss der Client noch im SCCM genehmigt werden:

linux09

linux11

linux12

Nun ist der Client im SCCM registriert. Das lokale Logfile des Clients befindet sich unter

/var/opt/microsoft/scxcm.log

Dieses kann z.B. mit Hilfe des Aufrufs

tail -F /var/opt/microsoft/scxcm.log

beobachtet werden.

Will man nun z.B. einen Policy-Abruf oder einen Hardware-Inventur-Zyklus erzwingen, ist dies mit den Aufrufen

/opt/microsoft/configmgr/bin/ccmexec -rs policy 

bzw.

/opt/microsoft/configmgr/bin/ccmexec -rs hinv

möglich.

Bei Bedarf kann man sich z.B. noch eine Gerätesammlung für alle Linux-/Unix-Systeme anlegen. Als begrenzende Sammlung verwendet man sinnvollerweise “Alle Systeme”. Die Mitgliedschaft lässt sich mit der Abfrageregel auf “Systemressource / Betriebssystem-Name und –Version” realisieren:

linux16  linux17  linux18

Viel Spaß beim Ausprobieren!

Microsoft wendet aktuelle Datenschutz-Standards für Azure an

 

Microsoft bestätigte diese Woche dass sie für Azure die aktuellsten Datenschutz-Standards umsetzen. Durch die Anwendung des ISO/IEC 27018 Standard verpflichtet sich Microsoft Kundendaten zu schützen, diese nicht zu Werbezwecken zu verwenden und bei einem Zugriff auf die Daten die Kunden vorab zu informieren.

“Links bearbeiten” ausblenden

Über den Punkt “Links bearbeiten” Haben Mitglieder einer Seite (also Nutzer mit mindestens Bearbeiten-Rechten) die Möglichkeit ihre Navigationselemente anzupassen.

image

Soll der Nutzer die Navigation nicht anpassen dürfen gibt es einen kleinen Trick ihm dieses Recht einfach zu nehmen, indem wir die Links mithilfe von CSS ausblenden. Dazu einfach den folgenden Code in die Vorlage der Master-Page einfügen:

.ms-navedit-editArea, ul.ms-core-listMenu-root .ms-navedit-editArea

{

display:none;

}

Im Ergebnis fehlen nun beide Links und die Navigation kann nicht mehr bearbeitet werden.

image

Drucker per GPO bereitstellen

Seit Windows Server 2008 und Windows Vista gibt es die Möglichkeit Drucker per Gruppenrichtlinien komfortabel bereitzustellen. Voraussetzungen hierfür:

  • AD-Funktionsebene mindestens Windows Server 2008
  • Drucker müssen über einen Printserver mit mindestens Windows Server 2008 freigegeben werden
  • Client-Betriebssystem mindestens Windows Vista
  1. Auf einem Domaincontroller in der Gruppenrichtlinienverwaltung erstellt man sich zunächst ein neues Gruppenrichtlinienobjekt.
  2. Auf dem Printserver muss die Rolle “Druck- und Dokumentdienste” installiert sein. Hier genügt der Rollendienst “Druckerserver”. Damit steht das Snap-In “Druckverwaltung” zur Verfügung.
  3. In dem Snap-In “Druckverwaltung” macht man unter “Drucker” auf den Drucker, den man bereitstellen möchte, einen Rechtsklick. Im Kontextmenü ist der Eintrag “Mit Gruppenrichtlinie bereitstellen” zu finden.

    Druckverwaltung1

  4. Über den Button “Durchsuchen” (1) wählt man das Gruppenrichtlinienobjekt aus, über das der Drucker bereitstellt werden soll (Hinweis: Wenn das GPO noch mit keiner OU verknüpft ist, muss auf die Registrierkarte “Alle” gewechselt werden, um das GPO zu sehen). Nach dem Auswählen des GPO muss noch definiert werden, ob der Drucker über die Computer- oder die Benutzerkonfiguration bereitgestellt werden soll (2).

    Druckverwaltung2

  5. Abschließend noch auf den Button “Hinzufügen” klicken, damit das GPO in die Liste aufgenommen wird. Es ist also ohne Probleme möglich einen Drucker über mehrere GPOs bereitzustellen!

    Druckverwaltung3

Das war’s! Sobald die Gruppenrichtlinien auf die entsprechenden Computer bzw. Benutzer angewandt werden, wird automatisch der Drucker installiert und steht damit zur Verfügung.

Um einen Drucker bei Computer bzw. Benutzer wieder zu entfernen, muss über die Druckverwaltung das Gruppenrichtlinienobjekt einfach wieder über “Mit Gruppenrichtlinie bereitstellen” aus der Liste entfernt 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 />

Newsfeed Posting per Code

Newsfeed Postings können in SharePoint auch mittels Client Object Model durchgeführt werden. Dafür muss die dll “Microsoft.SharePoint.Client.UserProfiles” aus dem ISAPI Verzeichnis referenziert werden.

Das Posting selbst muss mit einem ClientContext für den SP User durchgeführt werden. Ein Versuch mit einem App ClientContext schlägt fehl. Da in meinem BSP mit Office365 gearbeitet wird, müssen Credentials für SharePoint Online verwendet werden. (zeile 8 bis 11)

Ein Post wird mit der Methode CreatePost (Zeile 19) einer Instanz des SocialFeedManagers (Zeile 13) erstellt. Der erste Parameter ist die URL des Newsfeeds oder kann auch NULL sein. Im Falle von NULL wird das Posting im Newsfeed des User in seiner MySite erstellt.

Der gesamte Code:

   1:  var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
   2:   
   3:  using (var clientContext = new ClientContext(spContext.SPHostUrl))
   4:  {
   5:      string pwd = PASSWORD;
   6:      string userEmail = USERLOGIN;
   7:   
   8:      SecureString str = new SecureString();
   9:      pwd.ToList().ForEach(c => str.AppendChar(c));
  10:   
  11:      clientContext.Credentials = new SharePointOnlineCredentials(userEmail, str);
  12:   
  13:      SocialFeedManager socMan = new SocialFeedManager(clientContext);
  14:      SocialPostCreationData dat = new SocialPostCreationData
  15:      {
  16:          ContentText = TextBox1.Text + " " + DateTime.Now.ToShortTimeString()
  17:      };
  18:   
  19:      socMan.CreatePost(spContext.SPHostUrl.ToString(), dat);
  20:      clientContext.ExecuteQuery();
  21:   
  22:      TextBox1.Text = "";
  23:  }

App Part - Parameter

Serverseitige Webparts in SharePoint können konfiguriert werden. Gleiches möchte man euch bei App Parts erreichen. Jedoch werden SharePoint App Parts IFRAME in die Webseite eingebettet. Server Seitigen Code gibt es hier nicht.

Allerdings können in der Elements-Datei des App Parts Properties definiert werden. Im ClientWebPart Knoten der XML Datei ist folgender Eintrag möglich:

<Properties>
      <Property Name="Konfiguration1"
                Type="string"
                WebBrowsable="true"
                WebDescription="Test Konfiguration"
                WebDisplayName ="Konfig1"
                RequiresDesignerPermission="true"
                DefaultValue="">
      </Property>
    </Properties>

 

Dieser Eintrag ermöglicht das Konfigurieren. Allerdings muss der Konfigurationswert auch an der App part übergeben werden. Und dies wird in den Aufruf-Parameter der URL erledigt, jedoch muss dies ebenfalls konfiguriert werden.

Für jedes Property wird ein Platzhalter mit _ vor und nach dem Namen angelegt und dieser kann in der URL verwendet werden. Die URL ist ebenfalls in der Elements Datei beim Content Type anzugeben:

<Content Type="html" Src="~remoteAppUrl/Pages/DemoAppPart.aspx?{StandardTokens}&amp;Konfiguration1=_konfiguration1_&amp;EditMode=_editMode_" />

Training, Schulung, Februar Aktion

Month List