Event-based Async-Pattern in SharePoint hosted Apps mit TypeScript

TypeScript erlaubt richtige Software-Entwicklung im JavaScript-Umfeld. Klassen, Vererbung, Interfaces und Polymorphie sind nun auch in der Webentwicklung möglich. In Verbindung mit dem SharePoint Client Object Modell kann TypeScript auch genutzt werden, da seit einiger Zeit umfangreiche Type-Definition-Dateien existieren.

Allerdings beruht das CSOM auf einer asynchronen Ausführung des Codes. Sobald versucht wird, die Datenzugriffe auf SharePoint in TypeScript-Klassen zu verpacken, um mehr Übersichtlichkeit im Code zu erhalten, steht man vor dem Problem ein Asynchronous Pattern (z.B: Event-based Asynchronous Pattern (EAP))  zu implementieren.

Der Kernpunkt hierfür sind Events. Allerdings müssen diese in TypeScript erst selbst implementiert werden, da die Sprache hierfür noch keine Unterstützung anbietet.

Meine Implementation eines Events baut auf einer internen Invokation-List auf. Ein Array von Funktionen mit einer simplen Signatur. Um flexibel zu bleiben, werden Generics verwendet. Somit kann ich als Parameter jedes Objekt übergeben. Dann noch drei public-Functions um Handler zu registrieren, abzumelden und um das Event auszulösen. Fertig ist meine Event-Klasse Zwinkerndes Smiley

   1:  module Events {
   2:     export  class EventClass<T> {
   3:         private functions: { (param?: T): void; }[] = [];
   4:         public add(func: { (param?: T): void; }) {
   5:              this.functions.push(func);
   6:          }
   7:   
   8:         public remove(func: { (param?: T): void; }) {
   9:              this.functions = this.functions.filter(f => f !== func);
  10:          }
  11:   
  12:         public fireEvent(param?: T) {
  13:              if (this.functions) {
  14:                  this.functions.forEach(f => f(param));
  15:              }
  16:          }
  17:      }
  18:  } 

 

Die SharePoint-Seite

Das Ziel ist, eine typische SharePoint-Aktion, wie z.B. einen Task in einer Taskliste zu erstellen, in eine Klasse zu verpacken. Die Liste liegt im HostWeb. Um das Handling mit dem ClientContext für das Host Web einfacher zu gestalten, sind zunächst zwei Hilfsklassen sinnvoll:

   1:  class Url {
   2:      public  static getUrlVars  () : string[] {
   3:          var vars:string[] = [], hash;
   4:          var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
   5:          for (var i = 0; i < hashes.length; i++) {
   6:              hash = hashes[i].split('=');
   7:              vars.push(hash[0]);
   8:              vars[hash[0]] = hash[1];
   9:          }
  10:          return vars;
  11:      }
  12:   
  13:      public static getUrlVar (name:string):string {
  14:          return this.getUrlVars()[name];
  15:      }
  16:  }
  17:   
  18:  class SPContextHelper {
  19:      public hostContext: SP.AppContextSite;
  20:      constructor(public clientContext: SP.ClientContext) {
  21:          this.hostContext = this.getHostWebCtx(this.clientContext);
  22:      }
  23:   
  24:      private getHostWebCtx(ctx: SP.ClientContext): SP.AppContextSite {
  25:          var url = decodeURIComponent(Url.getUrlVar('SPHostUrl'));
  26:          return new SP.AppContextSite(ctx, url);
  27:      }
  28:  }

Die genaue Erklärung, wie das mit dem Zugriff auf das Hostweb so läuft, gibt’s hier.

Die Codezeilen, um einen Task anzulegen, habe ich in der Klasse “TaskHelper” gekapselt. Die Feldwerte des Tasks liegen in der Klasse “Task”. Der Taskhandler legt im Konstruktor einen Verweis auf die SharePoint-Liste an und initialisiert die vorher gezeigte SPContextHelper-Klasse.

   1:  class Task {
   2:      public Beschreibung: string;
   3:      public Faellig: Date;
   4:  }
   5:   
   6:  class TaskHandler {
   7:      private contextHelper: SPContextHelper;
   8:   
   9:      private liste: SP.List;
  10:   
  11:      public onSuccess: Events.EventClass<string> = new Events.EventClass<string>();
  12:      public onError: Events.EventClass<string> = new Events.EventClass<string>();
  13:   
  14:      constructor(public listenName: string) {
  15:          this.contextHelper = new SPContextHelper(SP.ClientContext.get_current());
  16:          this.liste = this.contextHelper.hostContext.get_web().get_lists().getByTitle(listenName);
  17:      }
  18:   
  19:      public AddTaskToSharePoint(task: Task) {
  20:          var neuesItem: SP.ListItem = this.liste.addItem(new SP.ListItemCreationInformation());
  21:          neuesItem.set_item('Title', task.Beschreibung);
  22:          neuesItem.set_item('DueDate', task.Faellig);
  23:          neuesItem.update();
  24:   
  25:          this.contextHelper.clientContext.executeQueryAsync(
  26:              (sender, args) => { this.onSuccess.fireEvent('its OK');},
  27:              (sender, args) => { this.onError.fireEvent(args.get_message());}
  28:              );
  29:      }
  30:  } 

In Zeile 11 und 12 werden die beiden Events onSuccess und onError angelegt. Es sind public Objekte. Sie werden mit dem Type “string” angelegt, d.h. die Message des Events ist ein simpler String.

In Zeile 26 und 27 sieht man das Auslösen des Events. Das sind die beiden Rückruf-Funktionen der executeQueryAsync-Methode des Client Context.

Der App-Code

Zu guter Letzt sollte dieser Code nun auch in der App verwendet werden.

Die TaskHandler-Instanz wird global initialisiert. In der Document-Ready-Funktion werden die beiden EventHandler, OKHandler und ErrorHandler an die beiden Events gehängt.

In der Funktion “addTask” wird dann nur noch die “AddTaskToSharePoint”-Methode des Handlers aufgerufen.

   1:  var taskHandler: TaskHandler = new TaskHandler("Aufgaben");
   2:  $(document).ready(function () {
   3:      taskHandler.onSuccess.add(OKHandler);
   4:      taskHandler.onError.add(ErrorHandler);
   5:  });
   6:   
   7:  function OKHandler(txt: string) {
   8:      alert(txt);
   9:  }
  10:   
  11:  function ErrorHandler(txt: string) {
  12:      alert(txt);
  13:  }
  14:   
  15:  function addTask() {
  16:      var titel: string = $('#txtBeschreibung').val();
  17:   
  18:      var t: Task = new Task();
  19:      t.Beschreibung = titel;
  20:      taskHandler.AddTaskToSharePoint(t);
  21:  } 
Kommentare sind geschlossen