In der Welt der SharePoint Ribbon Erweiterungen muss man zwingend auf JavaScript zurück greifen um Aktionen auszuführen.
Dafür gibt es ja das neue JavaScript Client Object Model. Doch dieses Framework stößt auch irgendwann an seine Grenzen.
So kann es z.B. bei lange andauernden, complexem Abfragen vorkommen, dass der Browser einfriert. Trotz Asynchronität.
Außerdem sollte man bei solchen Queries bedenkten, dass man viele Zeilen JavaScript Code schreiben muss, der viele Delegates enthalten wird. Dies führt zu einer schlechteren Lesbarkeit des Codes und bläht diesen nur unnötig auf.
Viel einfacher können solche Methoden serverseitig entwickelt werden. Und zwar durch den Einsatz eines Delegate-Controls in Verbindung mit einem IPostBackEventHandler.
Erweiterte PageComponente
Aber zuerst einmal alles zurück auf Anfang. Zunächst benötigt man eine PageComponente.
Ein PageComponent-Objekt ermöglicht das Interagieren mit dem Ribbon (http://msdn.microsoft.com/en-us/library/ff407303.aspx).
Type.registerNamespace('DL.PostbackRibbon');
// RibbonApp Page Component
DL.PostbackRibbon.PageComponent = function () {
DL.PostbackRibbon.PageComponent.initializeBase(this);
}
DL.PostbackRibbon.PageComponent.initialize = function () {
ExecuteOrDelayUntilScriptLoaded(
Function.createDelegate(
null,
DL.PostbackRibbon.PageComponent.initializePageComponent),
'SP.Ribbon.js');
}
DL.PostbackRibbon.PageComponent.initialize = function (controlId) {
DL.PostbackRibbon.PageComponent.ControlClientId = controlId;
ExecuteOrDelayUntilScriptLoaded(
Function.createDelegate(
null,
DL.PostbackRibbon.PageComponent.initializePageComponent),
'SP.Ribbon.js');
}
DL.PostbackRibbon.PageComponent.initializePageComponent = function () {
var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
if (null !== ribbonPageManager) {
ribbonPageManager.addPageComponent(DL.PostbackRibbon.PageComponent.instance);
ribbonPageManager
.get_focusManager()
.requestFocusForComponent(DL.PostbackRibbon.PageComponent.instance);
}
}
DL.PostbackRibbon.PageComponent.refreshRibbonStatus = function () {
SP.Ribbon.PageManager
.get_instance()
.get_commandDispatcher()
.executeCommand(Commands.CommandIds.ApplicationStateChanged, null);
}
DL.PostbackRibbon.PageComponent.ControlClientId = null;
DL.PostbackRibbon.PageComponent.prototype = {
init: function () {
// if you have something to initalize
},
getFocusedCommands: function () {
return [];
},
getGlobalCommands: function () {
// Server side commands will show up here
return getGlobalCommands();
},
isFocusable: function () {
return true;
},
canHandleCommand: function (commandId) {
return commandEnabled(commandId);
},
handleCommand: function (commandId, properties, sequence) {
return handleCommand(commandId, properties, sequence);
}
}
// Register classes
DL.PostbackRibbon.PageComponent.registerClass('DL.PostbackRibbon.PageComponent', CUI.Page.PageComponent);
DL.PostbackRibbon.PageComponent.instance = new DL.PostbackRibbon.PageComponent();
// Notify and execute jobs
NotifyScriptLoadedAndExecuteWaitingJobs("/_layouts/ListinfoRibbonPostbackCommand/DL.PostbackRibbon.PageComponent.js");
Hier gibt es auch die erste Anpassung die ich gemacht habe.
In Zeile 36 habe ich die Variable ControlClientId erstellt, die später die ID des IPostBackEventHandler hält. Somit wird sichergestellt, dass der richtige Handler benutzt wird.
Zeile 13 und 14 zeigen die angepasste Initialize-Methode, die die Control-ID setzt.
Ansonsten ist es eine einfache PageComponente wie in der MSDN beschrieben.
Das CommandHandler Control
Jetzt die spannende Serverseite. Ich habe ein Cotrol erstellt, das das Interface IPostBackEventHandler implementiert.
public class PostbackRibbonHandler : Control, IPostBackEventHandler
{
public PostbackRibbonHandler()
{
ID = "PostbackRibbonHandler";
}
protected override void CreateChildControls()
{
base.CreateChildControls();
// register server side command
List<IRibbonCommand> cmds = new List<IRibbonCommand>()
{
new SPRibbonPostBackCommand("RunPostback", this, "RunPostback", null)
};
SPRibbonScriptManager sm = new SPRibbonScriptManager();
// register the page component
sm.RegisterInitializeFunction(this.Page,
"InitPageComponent",
"/_layouts/ListinfoRibbonPostbackCommand/DL.PostbackRibbon.PageComponent.js",
false,
"DL.PostbackRibbon.PageComponent.initialize('" + this.ClientID + "')");
// enable server registered commands on the page
sm.RegisterGetCommandsFunction(this.Page, "getGlobalCommands", cmds);
sm.RegisterCommandEnabledFunction(this.Page, "commandEnabled", cmds);
sm.RegisterHandleCommandFunction(this.Page, "handleCommand", cmds);
}
public void RaisePostBackEvent(string eventArgument)
{
// implement PostBack logic
// by default eventArgument contains the name of the command
}
}
Im Konstruktor gebe ich dem Control einen Namen, damit man später einen PostBack auf dem Control abfeuern kann. Hier werden auch die Commands sowie die PageComponenten registriert die später von dem Control behandelt werden sollen.
Zeile 25 enthält den wichtigsten Schritt: Das setzen der Control-ID in die PageComponente.
PostBack ausführen
Nun wieder zurück zur PageComponente. In der Methode handleCommand wird der PostBack ausgelöst, der den IPostBackEventHandler triggert.
Dabei wird als Argument ein kleines JSON-Objekt übergeben, das mir die benötigten SharePoint Listen- und Iteminformationen bereitstellt.
handleCommand: function (commandId, properties, sequence) {
if (commandId === 'RunPostback') {
if (!CUI.ScriptUtility.isNullOrUndefined(DL.PostbackRibbon.PageComponent.ControlClientId)) {
var controlId = DL.PostbackRibbon.PageComponent.ControlClientId;
__doPostBack(controlId.replace('_', '$'), '{"id":"RunPostback","selectedId":' + SP.ListOperation.Selection.getSelectedItems()[0].id + ', "listId":"' + GetCurrentCtx().listName + '"}');
return true;
}
else {
if (window.console)
window.console.error('Unable to do a postback. PostBackHandler not found.')
}
}
else {
return handleCommand(commandId, properties, sequence);
}
}
Verarbeiten der Daten
Die PostBack Argumente kann man sehr schnell über den JavaScriptSerializer zu einem Objekt umwandeln.
Danach führt man wie gewohnt SharePoint Operationen aus.
In diesem Beispiel setze ich einen neuen Titel für das ausgewählte Dokument.
public void RaisePostBackEvent(string eventArgument)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
var args = jss.Deserialize<SPRibbonCommandArgs>(eventArgument);
switch (args.Id)
{
case "RunPostback":
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists[args.ListId];
if (list != null)
{
SPListItem item = list.GetItemById(args.SelectedId);
if (item != null)
{
SPField titleField = item.Fields.GetFieldByInternalName("Title");
item[titleField.Id] = "Mein neuer Titel";
item.SystemUpdate();
}
}
break;
}
}
[Serializable]
public class SPRibbonCommandArgs
{
/// <summary>
/// Command ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// ID of the Selected Item
/// </summary>
public int SelectedId { get; set; }
/// <summary>
/// ID of the List
/// </summary>
public Guid ListId { get; set; }
}
Ribbon Erweiterung und Delegate-Control
Dann fehlt ja “nur” noch die Ribbon definition. Diese besteht in meinem Fall aus einem einzelnen Button, der in jeder Dokumentenbibliothek eingeblendet wird.
Klickt man auf den Button wird das Klick-Event an die PageComponente geliefert und diese triggert per PostBack den IPostBackEventHandler.
In diese Datei kann man auch direkt das Delegate-Control registieren, wie in Zeile 34.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction Id="DL.PostbackCommand" Location="CommandUI.Ribbon" RegistrationId="101" RegistrationType="List">
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition Location="Ribbon.Documents.Copies" />
<CommandUIDefinition Location="Ribbon.Documents.Workflow" />
<CommandUIDefinition Location="Ribbon.Documents.Groups._children">
<Group Id="Ribbon.Documents.CustomGroup"
Sequence="55"
Description="Custom Group"
Title="Custom"
Template="Ribbon.Templates.Flexible2">
<Controls Id="Ribbon.Documents.CustomGroup.Controls">
<Button Id="Ribbon.Documents.CustomGroup.RunPostback"
Command="RunPostback"
Image16by16="/_layouts/images/ListinfoRibbonPostbackCommand/runit_32x32.png"
Image32by32="/_layouts/images/ListinfoRibbonPostbackCommand/runit_32x32.png"
LabelText="Postback!"
TemplateAlias="o2"
Sequence="15" />
</Controls>
</Group>
</CommandUIDefinition>
<CommandUIDefinition Location="Ribbon.Documents.Scaling._children">
<MaxSize Id="Ribbon.Documents.Scaling.CustomGroup.MaxSize"
Sequence="15"
GroupId="Ribbon.Documents.CustomGroup"
Size="LargeLarge" />
</CommandUIDefinition>
</CommandUIDefinitions>
</CommandUIExtension>
</CustomAction>
<Control Id="AdditionalPageHead" ControlAssembly="ListinfoRibbonPostbackCommand, Version=1.0.0.0, Culture=neutral, PublicKeyToken=098b81014c222ac1" ControlClass="ListinfoRibbonPostbackCommand.Controls.PostbackRibbonHandler"></Control>
</Elements>
That’s it.
Da diese ganze Mischung aus JavaScript, C# und XML auf den ersten Blick wohl etwas unübersichtlich ist gibt es hier noch das fertige Projekt zum Download.
ListinfoRibbonPostbackCommand.zip