code it

Martins Tech Blog

Mehrfachaktionen in kontextsensitiven Ribbons als Sandboxed Solution

Sharepoint 2010 bietet einige spannende Möglichkeiten, die es so bisher nicht gab. Zum einen gibt es das Client Object Model, das es erlaubt, auf Clientseite auf das Objektmodell von SharePoint zuzugreifen. Zum anderen bietet es die Möglichkeit, sogenannte Sandboxed Solutions bereitzustellen, die zwar mit geringeren Rechten ausgestattet sind, aber dafür von einer breiteren Anwendergruppe installiert werden können.

In diesem Blogpost wird gezeigt wie man diese Features kombinieren und in einer Aufgabenliste die Mehrfachaktion "Alle Aufgaben erledigen" implementieren kann, die dann als Sandboxed Solution bereitgestellt wird. Eine kurze Einführung in das Thema Sandboxed Solutions gibt Thorsten Hans in seinem Blogpost und auch das MSDN Magazine wartet mit einem Artikel dazu auf.

Erster Schritt ist es, ein neues SharePoint-Projekt in Visual Studio anzulegen. Dazu verwende ich das Projekttemplate "Empty SharePoint Project". Die Auswahl der Programmiersprache ist in diesem Fall irrelevant, da alles über XML und JavaScript gelöst wird. Visual Studio legt daraufhin eine neue Solution an, die bereits ein Feature beinhaltet. Allerdings ist der Name des Features recht unaussagekräftig und sollte direkt angepasst werden. In meinem Beispiel soll es TaskBatchCompleteFeature heißen. Hinzu kommt noch ein neues Element, da die Anpassungen über die Elements.xml durchgeführt werden. Damit auch hier klar ist, was das Element macht, bekommt es den Namen TaskBatchCompleteElement. Da die Solution komplett aus Konfigurationsdateien besteht, ist es sinnvoll, in den Projekteigenschaften noch "Include Assembly In Package" auf false zu setzen. Die Visual Studio Solution sollte nun ungefähr so aussehen:

 

Die nächsten Anpassungen erfolgen in der Elements.xml. Hier ist die Konfiguration der Ribbon-Anpassung zu hinterlegen. In der CustomAction definiert man, dass diese Aktion nur auf Listen des Typs Aufgabe im Ribbon sehen möchte. Dafür sind die Attribute RegistrationType und RegistrationId zuständig, wobei die List Template Id 107 für Aufgabenliste steht.

Über den Tag CommandUIDefinition kann man nun einen Button hinzufügen. Eine Kurzzusammenfassung, welche XML-Fragmente notwendig sind, um welches Ergebnis zu erreichen findet man in folgendem Post. Kurz erklärt definiert die Location dabei, in welcher Ribbon-Gruppe der Button hinzugefügt werden soll. Die Standard-Ribbons werden über die Datei CMDUI.xml im Ordner 14\template\global\xml definiert. Möchte man bestehende Ribbon-Gruppen anpassen, so kann man die Namen in dieser Datei ermitteln und auch anhand der bestehenden Sequence-Ids die Reihenfolge-Einordnung für das neue Control in der Gruppe bestimmen.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <CustomAction
    Id="TaskBatchCompleteRibbonAction"
    Location="CommandUI.Ribbon"
    RegistrationType="List"
    RegistrationId="107">
  <CommandUIExtension>
      
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.ListItem.Manage.Controls._children">
          <Button
              Id="TaskBatchCompleteRibbonButton"
              Alt="Aufgaben erledigen"
              LabelText="Aufgaben erledigen"
              Sequence="21"
              Command="TaskBatchCompleteRibbonCommand"
              Image32by32="_layouts/images/TaskDone.gif"
              Image16by16="_layouts/images/TaskDone.gif"
              TemplateAlias="o2"
              />
        </CommandUIDefinition>
      </CommandUIDefinitions>

      <!--TODO: add CommandUIHandler-->
    </CommandUIExtension>
    
    <!--TODO: add ScriptLink-->
  </CustomAction>
</Elements>

Da der Button nur dann aktiv sein soll, wenn mindestens ein Element gewählt wurde, wird ein CommandUIHandler hinzugefügt, der ein EnabledScript zugewiesen bekommt. Hier erfolgt die Prüfung auf die Anzahl der selektierten Elemente mit Hilfe der Funktion SP.ListOperation.Selection.getSelectedItems()

<CommandUIHandlers>
  <CommandUIHandler
     Command="TaskBatchCompleteRibbonCommand"
     CommandAction="javascript:BatchCompleteTasks();"
     EnabledScript="javascript:function isOneOrMoreEnabled() { 
                          var items = SP.ListOperation.Selection.getSelectedItems(); 
                          var ci = CountDictionary(items); 
                            return (ci > 0); 
                           } 
                          isOneOrMoreEnabled();" />
</CommandUIHandlers>

Als letzter Schritt wird nun noch die eigentliche Aktion benötigt. Diese wird in einer weiteren CustomAction hinterlegt. Die eigentliche Funktion ist recht simpel. Über die gleiche Methode wie eben werden die selektierten Elemente ermittelt und über die Kontextinformationen das zugehörige Web und die Liste geladen. Im Anschluss daran iteriert man durch die Elemente und setzt die Felder - bei einer Aufgabe sind das der Status und der Erledigungsgrad. Nachdem dies vorgenommen wurde, müssen die Daten noch zum Server übertragen werden. Dazu ruft man die Methode executeQueryAsync auf. Dieser übergibt man zwei Callbacks - in meinem Fall "success" und "failed", die dann noch eine Benachrichtigung für den Benutzer auslösen.

<CustomAction Id="TaskBatchCompleteRibbonScriptAction"
     Location ="ScriptLink"
     ScriptBlock="
BatchCompleteTasks = function () {
    var selectedItems = SP.ListOperation.Selection.getSelectedItems();
    var currentListGuid = SP.ListOperation.Selection.getSelectedList();

    var context = SP.ClientContext.get_current();
    var currentWeb = context.get_web();
    var currentList = currentWeb.get_lists().getById(currentListGuid);

    var k;
    for (k in selectedItems) {
        var listitem = currentList.getItemById(selectedItems[k].id);
        listitem.set_item('Status', 'Completed')
        listitem.set_item('PercentComplete', '1')
        listitem.update();
        context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed))
    }

}
             

function success() {
    SP.UI.Notify.addNotification('Aufgabe erfolgreich abgeschlossen');
}

function failed(sender, args) {
    var statusId = SP.UI.Status.addStatus(args.get_message());
    SP.UI.Status.setStatusPriColor(statusId, 'red');
    latestId = statusId;
}"
      />

Damit ist die Arbeit im Grunde auch schon getan. Nach einem Klick auf "Package" erscheint im Ausgabepfad eine wsp-Datei.

Sandboxed Solutions benötigen keine Farmadministratorrechte, um installiert zu werden. Aus diesem Grund erfolgt die Installation auch nicht in der Central Adminsitration sondern in der Solution Gallery der Site. Nachdem man die WSP-Datei hierher hochgeladen und die Solution aktiviert hat, steht das neue Feature in der Oberfläche zur Verfügung.

In Microsoft Access ein eigenes Ribbon erstellen

Seit Office 2007 glänzt Microsoft Access mit Ribbons statt klassischer Menüs. Die Ribbons sind erweiterbar und mit Hilfe von etwas XML kann man recht einfach eigene Ribbons erstellen.

Die Konfiguration wird in einer neuen Systemtabelle gespeichert. Dazu muss zunächst einmal sichergestellt sein, dass in den Navigationsoptionen der Haken bei Systemobjekte anzeigen gesetzt ist. 

 

Nun legt man eine neue Tabelle mit dem Namen USysRibbons an, mit folgenden Feldern:

NameDatentyp
RibbonName Text
RibbonXml Memo

Da der Ribbonname eindeutig sein muss, kann das entsprechende Feld auch gleich als Primärschlüssel verwendet werden - es spricht aber auch nichts dagegen, noch ein weiteres Feld (z.B. Id) anzufügen und dieses als Primärschlüssel zu definieren.

Nun kann man in die Tabelle seine Ribbon-Definition eintragen. Wie das XML genau aussehen muss, entnimmt man dabei am besten der MSDN. Eine sehr gute Seite zu dem Thema ist auch accessribbon.de. Möchte man das XML nicht manuell erstellen, so können hier die Visual Studio Tools for Office (Export Ribbon to Xml) oder der Ribbon-Creator helfen.

Nachdem man den Datensatz angelegt und das Ribbon in den Access-Optionen als Standard definiert hat, wird nun ab dem nächsten Start derDatenbank das eben erstellte Ribbon angezeigt.

Ein kleiner Tipp noch am Rande: Problemlos können die bereits implementierten Bilder verwendet werden. Dazu muss nur im Tag imageMso ein gültiger Wert angegeben werden. Leider gibt es dafür keine offizielle Dokumentation von Microsoft. Welche Werte gültig sind, kann man ganz leicht selbst ermitteln, indem man in den Einstellungen der Schnellzugriffsleiste mit der Maus über die verfügbaren Symbole fährt. Im Tooltipp steht die Id des Bildes in Klammern dahinter.