在SharePoint中,如果手动触发工作流,用户需要选中item,然后点击workflow,导向另外一个页面,才可以启动工作流。
这里我想添加一个Ribbon button,这个Ribbon button可以是一个下拉菜单,列出所有可以触发的workflow,用户只需要选中一些item,然后直接点击下拉菜单中的一个就可以触发工作流,不需要页面转向。
下面是具体实现方法的介绍:
第一步,我们添加一个Ribbon Button到Ribbon中。
我使用flyout anchor类型的Ribbon button,将其添加到Ribbon的Ribbon.ListItem.Workflow这个组中:

Ribbon的配置代码:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Control
Id="AdditionalPageHead"
Sequence="200"
ControlClass="shrenky.projects.workflowtrigger.RibbonLoaderControl"
ControlAssembly="$SharePoint.Project.AssemblyFullName$">
</Control>
<CustomAction Id="shrenky.projects.workflowtrigger.Ribbon"
Location="CommandUI.Ribbon"
RegistrationId="100"
RegistrationType="List"
Sequence="120"
Title="Tigger"
Description="Trigger workflow">
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition Location="Ribbon.ListItem.Workflow.Controls._children">
<FlyoutAnchor Id="shrenky.projects.workflowtrigger.Anchor"
Sequence="20"
LabelText="Workflows"
Image32by32="/_layouts/15/images/shrenky.projects.workflowtrigger/Trigger.jpg"
PopulateDynamically="true"
PopulateOnlyOnce="false"
PopulateQueryCommand="shrenky.projects.workflowtrigger.PopulateMenus"
ToolTipTitle="Dynamic dropdown"
ToolTipDescription="Shows dropdown made of buttons defined in JavaScript"
TemplateAlias="o1">
</FlyoutAnchor>
</CommandUIDefinition>
</CommandUIDefinitions>
<CommandUIHandlers>
<CommandUIHandler Command="shrenky.projects.workflowtrigger.TriggerMenuClick" CommandAction="javascript:WorkflowTrigger.Ribbon.RibbonComponent.get_instance().TriggerWorkflow(arguments[2].MenuItemId, '');" EnabledScript="true" />
<CommandUIHandler Command="shrenky.projects.workflowtrigger.MessageMenuClick" CommandAction="javascript:alert('No workflow associated on current list.')" EnabledScript="true" />
</CommandUIHandlers>
</CommandUIExtension>
</CustomAction>
</Elements>
首先添加了一个delegate control,用来注册一些脚本比如JQuery,SPServices和Ribbon需要使用的PageComponent等等。
然后定义了一个FlyoutAnchor按钮,这个按钮没有配置下拉菜单,因为下拉菜单是通过PopulateQueryCommand这个属性来动态生成的,当用户点击这个按钮时,会执行“shrenky.projects.workflowtrigger.PopulateMenus”这个方法,这个方法是定义在PageComponent中的,用来构造下拉菜单的xml。
除了FlayoutAnchor之外还定义了两个CommandUIHandler,一个是"shrenky.projects.workflowtrigger.TriggerMenuClick",供用户点击FlyoutAnchor动态生成的下拉菜单的时候调用,另外一个暂时没有用处。
delegate control除了用来加载脚本之外,还有一个用途就是将当前list上的所有关联的workflow的信息(association id,Title,Description)等等输出到页面上供之后的操作使用。
第二步,当用户点击Ribbon的时候,显示当前列表中所有可以触发的工作流
向Flyout Anchor动态添加下拉菜单是通过PageComponent(WorkflowTriggerPageComponent.js文件)实现的,关于pagecomponent的介绍可以参考《SharePoint 2010 as development platform》这本书中的例子。具体定义如下:
Type.registerNamespace('WorkflowTrigger.Ribbon');
WorkflowTrigger.Ribbon.RibbonComponent = function () {
WorkflowTrigger.Ribbon.RibbonComponent.initializeBase(this);
}
WorkflowTrigger.Ribbon.RibbonComponent.get_instance = function () {
if (!WorkflowTrigger.Ribbon.RibbonComponent.s_instance) {
WorkflowTrigger.Ribbon.RibbonComponent.s_instance = new WorkflowTrigger.Ribbon.RibbonComponent();
}
return WorkflowTrigger.Ribbon.RibbonComponent.s_instance;
}
WorkflowTrigger.Ribbon.RibbonComponent.prototype = {
focusedCommands: null,
globalCommands: null,
registerWithPageManager: function () {
SP.Ribbon.PageManager.get_instance().addPageComponent(this);
SP.Ribbon.PageManager.get_instance().get_focusManager().requestFocusForComponent(this);
},
unregisterWithPageManager: function () {
SP.Ribbon.PageManager.get_instance().removePageComponent(this);
},
init: function () { },
getFocusedCommands: function () {
return ['shrenky.projects.workflowtrigger.PopulateMenus'];
},
getGlobalCommands: function () {
return ['shrenky.projects.workflowtrigger.PopulateMenus'];
},
canHandleCommand: function (commandId) {
if (commandId === 'shrenky.projects.workflowtrigger.PopulateMenus') {
return true;
}
else { return false; }
},
handleCommand: function (commandId, properties, sequence) {
if (commandId === 'shrenky.projects.workflowtrigger.PopulateMenus') {
properties.PopulationXML = this.GetDynamicMenuXml();
}
else {
return handleCommand(commandId, properties, sequence);
}
},
GetDynamicMenuXml: function () {
... ...
},
selectedItems: [],
workflowAssociationId: null,
TriggerWorkflow : function(workflowAssoId, param)
{
... ...
},
TriggerWorkflowExec: function ()
{
... ...
},
TriggerWorkflowFailed: function ()
{
... ...
}
}
WorkflowTrigger.Ribbon.RibbonComponent.registerClass('WorkflowTrigger.Ribbon.RibbonComponent', CUI.Page.PageComponent);
WorkflowTrigger.Ribbon.RibbonComponent.get_instance().registerWithPageManager();
NotifyScriptLoadedAndExecuteWaitingJobs("WorkflowTriggerPageComponent.js");
动态生成下拉菜单是通过以下代码来实现的:
if (commandId === 'shrenky.projects.workflowtrigger.PopulateMenus') {
properties.PopulationXML = this.GetDynamicMenuXml();
}
GetDynamicMenuXm这个方法负责产生下拉菜单的xml,在产生xml的时候,需要用到delegate control事先输出到页面上的workflow的信息(即workflowtrigger.data.WorkflowData这个变量,一个json对象,包含了workflow的具体信息例如id,Title等)
GetDynamicMenuXml: function () {
var counter = 0;
var data = workflowtrigger.data.WorkflowData;
var xml = '<Menu Id = "shrenky.projects.workflowtrigger.Anchor.Menu">'
+ '<MenuSection Id="shrenky.projects.workflowtrigger.Anchor.Menu.MenuSection1" >'
+ '<Controls Id="shrenky.projects.workflowtrigger.Anchor.Menu.MenuSection1.Controls">';
var len = data.length;
for (var i = 0; i < len; i++){
counter = counter + 1;
var current = data[i];
var workflowAssociationId = current.WorkflowAssociationId;
var workflowName = current.WorkflowTitle;
var workflowDesc = current.WorkflowDescription;
var buttonXml = String.format(
'<Button Id= "shrenky.projects.workflowtrigger.Anchor.Menu.MenuSection1.Menu{0}" '
+ 'Command="shrenky.projects.workflowtrigger.TriggerMenuClick" '
+ 'MenuItemId="{1}" '
+ 'LabelText="{2}" '
+ 'ToolTipTitle="{2}" '
+ 'ToolTipDescription="{3}" TemplateAlias="o1"/>', counter, workflowAssociationId, workflowName, workflowDesc);
xml += buttonXml;
}
if (counter === 0) {
var msgXml = String.format(
'<Button Id= "shrenky.projects.workflowtrigger.Anchor.Menu.MenuSection1.Menu{0}" '
+ 'Command="shrenky.projects.workflowtrigger.MessageMenuClick" '
+ 'MenuItemId="1" '
+ 'LabelText="{0}" '
+ 'ToolTipTitle="{0}" '
+ 'ToolTipDescription="{0}" TemplateAlias="o1"/>', 'No workflow associated on current list');
xml += buttonXml;
}
xml += '</Controls>' + '</MenuSection>' + '</Menu>';
return xml;
},
在构造xml的时候,我们把下拉菜单的Command属性赋予了“shrenky.projects.workflowtrigger.TriggerMenuClick”这个方法,就个方法就是之前定义的CommandUIHandler其中之一。当用户点击下拉菜单时,触发“shrenky.projects.workflowtrigger.TriggerMenuClick”这个方法,这个方法实际上是调用了另外一个方法TriggerWorkflow:
selectedItems: [],
workflowAssociationId: null,
TriggerWorkflow : function(workflowAssoId, param)
{
this.workflowAssociationId = workflowAssoId;
var ctx = SP.ClientContext.get_current();
var web = ctx.get_web();
var lists = web.get_lists();
var listId = SP.ListOperation.Selection.getSelectedList();
var list = lists.getById(listId);
var items = SP.ListOperation.Selection.getSelectedItems();
this.selectedItems = [];
if (items.length > 0) {
for (var i in items) {
var id = items[i].id;
var item = list.getItemById(id);
this.selectedItems.push(item);
ctx.load(item);
}
ctx.executeQueryAsync(Function.createDelegate(this, this.TriggerWorkflowExec), Function.createDelegate(this, this.TriggerWorkflowFailed));
}
else {
alert("Please select item");
}
},
这个方法首先获取用户选择的所有item,将这些item保存在selectedItems这个变量中,然后使用executeQueryAsync方法获取这些item的信息,供最后在这些item上触发工作流使用。如果这些item的信息获取成功,则会调用回调函数TriggerWorkflowExec,正式触发工作流了:
TriggerWorkflowExec: function ()
{
for (var i in this.selectedItems) {
var item = this.selectedItems[i];
var itemId = item.get_item("ID");
var itemTitle = item.get_item("Title");
var itemFileRef = item.get_item("FileRef");
var webUrl = _spPageContextInfo.webAbsoluteUrl;
var url = webUrl + itemFileRef;
var id = this.workflowAssociationId;
!function outer(id, url, itemTitle) {
$().SPServices({
debug: true,
operation: "StartWorkflow",
async: true,
item: url,
templateId: id,
workflowParameters: "<Data/>",
completefunc: function () { SP.UI.Notify.addNotification("Start workflow on " + itemTitle + " Successfully", true); }
});
}(id, url, itemTitle);
}
},
这里遍历用户选择的items,使用SPServices触发工作流,使用SPServices触发工作流的方法参见: 点击打开链接。
第三步, 选中需要启动的工作流然后使用SPServices来触发工作流,工作流正常启动之后会有消息通知(这里“Thinkpad”是item的title):
目前这个工具只能运行在列表上,还很不完善,但是是一个很好的自定义ribbon的一个例子。
具体代码可以在github上下载:https://github.com/shrenky/sharepointprojects.git