Eigene Ribbon-Controls, EnabledScript und asynchrones JavaScript

In diesem Beitrag soll es nicht darum gehen, wie man dem SharePoint Ribbon (Menüband oder Multifunktionsleiste) eigene Controls mit eigenen Funktionen hinzufügt. Es gibt bereits viele Beiträge dazu, wie z.B. Fabian Moritz: SharePoint 2010 Ribbon anpassen und erweitern oder Chris O’Brien: Customizing the ribbon. Außerdem gibt es hier eine Serie von Wictor Wilén, in der er ausführlich die einzelnen Controls beschreibt (derzeit noch nicht ganz vollständig).

In den einzelnen Artikeln wird zum Teil auch gezeigt, wie man mit Hilfe des EnabledScript-Attributs beim CommandUIHandler dafür sorgen kann, daß ein Control nur unter bestimmten Umständen aktiv ist. Im EnabledScript kann dazu JavaScript hinterlegt werden, das die Randbedingungen prüft und entsprechend true oder false zurückliefert. Die SharePoint-Infrastruktur sorgt dann dafür, daß ein Control aktiv oder nicht (ausgegraut) ist.

Das Problem daran ist, daß dieses Script immer synchron ausgeführt werden muß, d.h. es muß direkt true oder false zurückgeben. In vielen Fällen reicht das aus, z.B. wenn ein Control nur dann aktiv sein soll, wenn in einer Listenansicht genau ein Element ausgewählt ist. Sobald man aber mehr Informationen über dieses ausgewählte Element braucht, muß man diese über die SharePoint Client-API für JavaScript ermitteln und derartige Aufrufe erfolgen immer asynchron.

In diesem Beitrag soll deshalb gezeigt werden, wie man Ribbon-Controls auch über asynchrone JavaScript-Funktionen aktivieren bzw. deaktivieren kann. Ich verwende dabei aus Gewohnheit den "alten" Begriff JavaScript. Der Kern dieser Technologie ist schon länger unter dem Namen ECMAScript standardisiert, weshalb die Begriffe JavaScript und ECMAScript oft synonym behandelt werden.

Im Beispiel soll von einer Aufgabenliste ausgegangen werden, bei der ein Control im Ribbon nur dann aktiv sein soll, wenn genau eine Aufgabe ausgewählt ist, deren Status nicht "Abgeschlossen" ist.

Damit das Ganze etwas übersichtlicher wird, lagert man das Script in eine eigene *.js Datei aus, die per ScriptLink eingebunden wird. Im EnabledScript ruft man dann nur eine Funktion in dieser Datei:

EnabledScript="javascript:myEnabledFunction();"

Innerhalb der Funktion wird zunächst die Anzahl der ausgewählten Elemente geprüft. Ist diese ungleich eins, dann wird sofort false zurückgegeben. Weitere Tests sind in diesem Fall nicht notwendig:

var selectedItems = SP.ListOperation.Selection.getSelectedItems();

if (CountDictionary(selectedItems) != 1) {

  return false;

}

Jetzt haben wir also den Fall, bei dem genau ein Element ausgewählt ist. Für dieses Element wollen wir jetzt über einen asynchronen Aufruf den aktuellen Wert des Statusfeldes holen und erst dann entscheiden, ob das Control aktiviert oder deaktiviert werden soll. Wir brauchen dazu zwei globale Variablen: eine für das eigentliche Listenelement und eine zum Zwischenspeichern des Ergebnisses:

var myListItem = null;

var myButtonEnabled = false;

Jetzt wird geprüft, ob das Listenelement bereits initialisiert wurde und wenn ja, ob dieses Element dieselbe ID besitzt, wie das aktuell ausgewählte Element (durch die Asynchronität könnte sich das in der Zwischenzeit geändert haben). Ist das alles der Fall, dann haben wir bereits ein Ergebnis (wie das ermittelt wurde s.u.) und können dieses direkt zurückgeben:

var itemId = selectedItems[0]["id"];

if (this.myListItem != null && this.myListItem.get_item("ID") == itemId) {

  return this.myButtonEnabled;

}

Andernfalls holen wir uns das Listenelement über einen asynchronen Funktionsaufruf:

else {

  var ctx = new SP.ClientContext();

  var web = ctx.get_web();

  var list = web.get_lists().getById(SP.ListOperation.Selection.getSelectedList());

  this.myListItem = list.getItemById(itemId);

  ctx.load(this.myListItem, "ID", "Status");

  ctx.executeQueryAsync(enabledQuerySuccess, enabledQueryFailed);

}

Beim Aufruf von executeQueryAsync werden zwei Funktionen übergeben. EnabledQueryFailed wird aufgerufen, wenn irgendetwas schiefläuft:

function enabledQueryFailed(s, args) {

  myButtonEnabled = false;

  myListItem = null;

}

Dabei werden einfach die Variablen zurückgesetzt, das Control bleibt deaktiviert und es passiert nichts weiter. Zu Debuggingzwecken kann man dort auch eine Fehlermeldung, z.B. über die alert-Funktion, ausgeben:

alert(‚Fehler: ‚ + args.get_message());

In der Funktion enabledQuerySuccess wird der aktuelle Wert des Statusfeldes geprüft und in Abhängigkeit davon die globale Variable zum Aktivieren bzw. Deaktivieren des Controls gesetzt:

function enabledQuerySuccess() {

  var status = myListItem.get_item("Status");

  if (status == "Abgeschlossen") {

    myButtonEnabled = false;

  } else {

    myButtonEnabled = true;

  }

  RefreshCommandUI();

}

Über einen Aufruf der SharePoint-Funktion RefreshCommandUI wird veranlaßt, daß für das Ribbon erneut geprüft wird, welches Control aktiv sein soll und welches nicht. Dadurch wird unsere Funktion myEnabledFunction erneut aufgerufen. Allerdings wird dort jetzt festgestellt, daß wir alle notwendigen Informationen bereits besitzen und nur noch das Ergebnis zurückgegeben.

Zusammenfassend hier nochmal der gesamte Scriptcode:

var myListItem = null;

var myButtonEnabled = false;

 

function myEnabledFunction() {

  var selectedItems = SP.ListOperation.Selection.getSelectedItems();

  if (CountDictionary(selectedItems) != 1) {

    return false;

  }

  var itemId = selectedItems[0]["id"];

  if (this.myListItem != null && this.myListItem.get_item("ID") == itemId) {

    return this.myButtonEnabled;

  } else {

    var ctx = new SP.ClientContext();

    var web = ctx.get_web();

    var list = web.get_lists().getById(SP.ListOperation.Selection.getSelectedList());

    this.myListItem = list.getItemById(itemId);

    ctx.load(this.myListItem, "ID", "Status");

    ctx.executeQueryAsync(enabledQuerySuccess, enabledQueryFailed);

  }

}

 

function enabledQueryFailed(s, args) {

  myButtonEnabled = false;

  myListItem = null;

}

 

function enabledQuerySuccess() {

  var status = myListItem.get_item("Status");

  if (status == "Abgeschlossen") {

    myButtonEnabled = false;

  } else {

    myButtonEnabled = true;

  }

  RefreshCommandUI();

}

 

4 Gedanken zu “Eigene Ribbon-Controls, EnabledScript und asynchrones JavaScript

  1. Hallo, vielen Dank für den hilfreichen Beitrag. Können Sie mir sagen, warum die executeQueryAsnc Methode nicht in einer Schleife funktioniert?
    Ich wollte eine Möglichkeit für Multiselect schaffen a la:

    for (count=0; count < CountDictionary(selectedItems); count++) {
    ctx.load(this.myListItem, "ID", "E_x002d_Mail");
    ctx.executeQueryAsync(enabledQuerySuccess, enabledQueryFailed)
    }

    Gefällt mir

  2. Das liegt daran, daß die Methode asynchron arbeitet. Der erste Aufruf in der Schleife funktioniert und die Schleife läuft sofort weiter und macht einen zweiten Aufruf. Wenn ein solcher Aufruf beendet ist, wird die Callback-Methode (enabledQuerySuccess) aufgerufen.

    Wenn man das umgehen möchte, braucht man statt myListItem ein Array von list items. In der Schleife setzt man dann jeweils eines davon und ruft jedesmal ctx.Load(this.myItems[i], …) auf. Der Aufruf von executeQueryAsync erfolgt dann erst nach der Schleife.

    Andi

    Gefällt mir

  3. Hi, This is Siddiqali from India,I work as SP developer.I am leianrng branding the SharePoint 2010 site ,I request you to tell me How can I Integrate SharePoint 2010 Theme: Tendance in my local site I am eager to do this in my local system.Hope you respond to me as soon as possible.Thanks,Quality Communication ProvidesQuality Work.MOHAMMAD SIDDIQALI

    Gefällt mir

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s