SPServices Stories #1 – How to Start a Workflow on Multiple Items in a List

This entry is part 1 of 21 in the series SPServices Stories

Introduction

Given the fact that so many people are using SPServices these days, I wanted to start posting some of the great things that people are doing with it out there.

If you have a story about how you are using SPServices that you would like to tell, ping me using the contact form. Your story doesn’t even have to include code, though people love to see examples. I’m always interested in what impact using SPServices may have had on your development philosophy, time to market with solutions, hiring practices, really anything that you feel SPServices has enabled you to do.

We can remove any identifying details if you feel that need to do so, but I’d like these stories to show off what *you* have done, so it’s great if you can take credit. I reserve the right to do a little editing for clarity, but otherwise you can write your own story. I’m also happy to help.

The first guest post is from fellow SharePoint MVP, Alberto Diaz Martin, who lives in Spain. Thanks to Alberto for kicking this series off!

[important]You can also read this post in Spanish on Alberto’s blog SharePoint 2010. Iniciar un flujo de trabajo en múltiples elementos de una lista[/important]

How to Start a Workflow on Multiple Items on a List

In SharePoint 2010 we have the option to select multiple items on a list. When you select several items, the ribbon allows you to Delete Items and Send Alerts, but where is the Workflow command?

To start a workflow on a list item, you have to go through the Start workflow page and if the workflow has an initialization form, you also have to enter the parameters. Because of this, SharePoint doesn’t allow users to start a workflow on multiple items simultaneously. But why not do so when we have a parameter-less workflow?

I think this is a missing feature on SharePoint 2010 because we can certainly do it using the SharePoint API or Web Services without any problems. What can we do to provide this capability to our users?

First, we need to create a Ribbon command using a Custom Action and in this action we will have two options to try to start the workflow. The first one uses an application page by passing the selected items as parameters and uses the server API to start the process. The second, and more flexible and elegant option is using JavaScript and the SharePoint Web Services to start each workflow per item.

SPServices Workflow Ribbon Custom Action

SharePoint Web Services are a horrible way to talk with SharePoint [ed: I disagree, but everyone is entitled to their opinion!] because the Web Services use XML to get and put parameters and options, and it’s not easy working with XML in JavaScript.

SPServices to the Rescue!!

As you know, SPServices is a jQuery library which encapsulates SharePoint Web Services with jQuery to make it easy to call them. SPServices has a Workflow namespace with some powerful operations and we can use StartWorkflow to start an item workflow, even if it has parameters.

It is so easy to use, you only need the Item URL, the workflow template Id and, if required, the workflow parameters.

$().SPServices({
  operation: "StartWorkflow",
  item: currentItemURL,
  templateId: workflowGUID,
  workflowParameters: workflowParams,
  async: true,
  completefunc: function () {
    SP.UI.Notify.addNotification("Workflow process started on selected item.", false);
  }
});

To get the workflow template Id, we have another function called GetTemplatesForItem that returns all the associated workflows for an item. All we have to do is get all the templates and find our workflow by name.

$().SPServices({
  operation: "GetTemplatesForItem",
  item: itemURL,
  async: true,
  completefunc: function (xData, Status) {
    var currentItemURL = this.item;
    $(xData.responseXML).find("WorkflowTemplates > WorkflowTemplate").each(function (i, e) {
      if ($(this).attr("Name") == "Invoice Approve") {
        var guid = $(this).find("WorkflowTemplateIdSet").attr("TemplateId");
        if (guid != null) {
          workflowGUID = "{" + guid + "}";
          //in this point, we have our workflow Id and we have to call the starting method
        }
      }
    }
  }
})

Now, we have to traverse the selected items in the custom action method, and for each item call the SPServices StartWorkflow method. Something like this:

function StarSignWorkflow(listId) {

  RemoveAllStatus(true);
  waitDialog = SP.UI.ModalDialog.showWaitScreenWithNoClose('Starting approval workflow process on selected item','Please,wait until we finished this long operation.',76,400);

  //Get the selected items
  clientContext = new SP.ClientContext.get_current();
  var web = clientContext.get_web();
  var list = web.get_lists().getById(listId);
  var items = SP.ListOperation.Selection.getSelectedItems(ctx);
  totaSelItems = items.length;

  //Because only have items Id,we need to use Client Object Model to get EncodeAbsUrl.
  var query = new SP.CamlQuery();
  var queryString = '<View><Query><Where><In><FieldRef Name="ID"/><Values>';
  for (index in items) {
    var valueString = '<Value Type="Integer">' + items[index].id + '</Value>';
    queryString = queryString + valueString;
  }

  query.set_viewXml(queryString + '</Values></In></Where></Query></View>');
  this.collListItems = list.getItems(query);
  clientContext.load(collListItems,'Include(EncodedAbsUrl)');

  //In the success callback,we’ll have all the selected items with the absolute url.
  clientContext.executeQueryAsync(Function.createDelegate(this,this.onInitProcessSuccess),Function.createDelegate(this,this.onInitProcessFail));
}

function onInitProcessSuccess() {

  var listItemEnumerator = this.collListItems.getEnumerator();

  //If our workflow has default init param,we can provide it in this way to run workflow with default values.
  var workflowParams = "<Data><Approvers></Approvers><NotificationMessage></NotificationMessage>" +
      "<DurationforSerialTasks></DurationforSerialTasks><DurationUnits></DurationUnits>" +
      "<CC></CC><CancelonRejection></CancelonRejection><CancelonChange></CancelonChange>" +
   "<EnableContentApproval></EnableContentApproval></Data>";

  try {
    var counter = 1;
    var total = totaSelItems;

    //Traverse all the selected items
    while (listItemEnumerator.moveNext()) {
      var oListItem = listItemEnumerator.get_current();
      var itemURL = oListItem.get_item('EncodedAbsUrl');
      var workflowGUID = null;

      //Before start the workflow,we used GetTemplatesForItem to get Workflow Template Id.
      $().SPServices({
        operation: "GetTemplatesForItem",
        item: itemURL,
        async: true,
        completefunc: function (xData,Status) {
          var currentItemURL = this.item;
          $(xData.responseXML).find("WorkflowTemplates > WorkflowTemplate").each(function (i,e) {
            if ($(this).attr("Name") == "Invoice Approve") {
              var guid = $(this).find("WorkflowTemplateIdSet").attr("TemplateId");
              if (guid != null) {
                workflowGUID = "{" + guid + "}";
                $().SPServices({
                  operation: "StartWorkflow",
                  item: currentItemURL,
                  templateId: workflowGUID,
                  workflowParameters: workflowParams,
                  async: true,
                  completefunc: function () {
                    if (total == counter) {
                      if (waitDialog != null) {
                        waitDialog.close();
                      }
                      SP.UI.Notify.addNotification("Started workflow approved process for selected invoice.",false);
                      window.location.reload();
                    }
                    counter++;
                  }
                });
              }
            }
          });
        }
      });
    }
  }catch (e) {
    if (waitDialog != null) {
      waitDialog.close();
    }
    AddStatus("There is an exception. Error: " + e.message,"red");
  }
}

As you can see, you have an easy way to provide an easy way to start a process on multiple items at the same time. Thanks to SPServices, working with SharePoint client side is more flexible and easy.

AlbertoDiazMartinAlberto Diaz Martin
MVP SharePoint
adiazcan@hotmail.com
@adiazcan
http://geeks.ms/blogs/adiazmartin

Series NavigationSPServices Stories #2 – Charting List Data with HighCharts >>

46 Comments

    • docsridhar:

      There are many posts here on my blog as well as elsewhere that take you through the steps to implement script-based solutions. If you have specific questions about this specific method, please post them back, but otherwise you’ll need to do more homework.

      M.

      Reply
      • Hi Marc I have a specific question about the Custom Action. For some reason I cannot get the javascript right. Would you mind sharing that bit of information? Thank you.

        Reply
        • Chris:

          The Custom Action is really the deployment part. You should be able to get the script working by simply including it in a page. How you decide to package it up for deployment is up to you.

          M.

          Reply
          • Sorry Marc maybe I misspoke. I have the script and everything in the page. When I select the items and push the custom action (ribbon button). That javascript is the bit I am having trouble with to call the script from the ribbon button. Thank you for any assistance.

            Reply
  1. Hi Marc,
    Thank you so much for your great work!
    I have problems fully understanding your script. To be specific in lines 14, 16 and 19 you are concatenating empty strings. I always thought there should be CAML expressions?
    In the current script the id numbers are connected without any space in between. Or have I missed sth.?

    Could you give me some Information on that please?

    Best regards from Austria!

    Reply
  2. Hello
    I´ve been checking your SPServices Package,
    What i see, or well did not see, is any reference to sharepoint 2013 or the apps model.

    I think this is well suitable when developing sandboxes solutions or farm solutions, and not suites for apps right?

    Thanks

    Reply
    • JLSF:

      I haven’t done extensive testing with SharePoint 2013, but the SOAP Web Services are very much alive and kicking, though they have been deprecated. Some would say that the right way to go for apps is the SharePoint CSOM, but the SOAP Web Services still provide some capabilities that aren’t there yet.

      M.

      Reply
  3. Is it possibile to run a site workflow not assigned to any list? I’m getting 500 “Internal Server Error”. I’m setting url to the site.
    $().SPServices({
    operation: “StartWorkflow”,
    async: true,
    item: “http://server”,
    templateId: templateId,
    workflowParameters: workflowParameters,
    completefunc: function (xData) {
    alert(“ok”);
    }
    });

    soap:ServerException of type ‘Microsoft.SharePoint.SoapServer.SoapServerException’ was thrown.itemUrl

    Reply
    • frymi:

      The SOAP Web Services were written long before there was such a thing as a site workflow. Some of them were updated [slightly] for SharePoint 2010, but it doesn’t look like this was one of them. In fact, the SharePoint 2010 Web Services documentation is missing the Workflow Web Service altogether, even though we can easily see that it lives on at /_vti_bin/workflow.asmx.

      M.

      Reply
  4. Hi, is it possible to consolidate mutiple items in one list using the workflow or other ways? I am trying to combine several items in the list to create a new document in the library, using workflow. Right now, once editing or adding a item in the associated list, I can only get a seperate document. Hope I can find solutions…

    Reply
  5. Marc,
    I get this and I can implement it. The key issue I am having is that the Custom Action that I add in the ribbon will light up when I select one item. As soon as I pick 2 or more items, the custom action greys out. I have scoured the web and it looks like I am doing things correctly. In my custom “View Ribbon” action I chose “Navigate To URL:” and enter the following line: javascript:openChildWindow(‘http://path-to-my-site/Site Pages/PrintRoomPage.htm’); The function gets called and everything works, except that the custom action only lights up when I have one item selected.

    Any thoughts?

    Reply
  6. OK… This is really dumb. Inside SharePoint Designer (2010) when you have the list open to the page when you can set general list information. There are two ways to add a custom action:

    Method 1:
    1. Go to the Custom Actions Tab
    2. Click on “Custom Actions” then on the drop down, click on “View Ribbon”
    Result: You wind up creating a new action that works on only 1 item, no matter what you do next.

    Method 2:
    1. Go to the List Settings Tab
    2. Click on “Custom Actions” then on the drop down, click on “View Ribbon”
    Result: You can now create a new action that works on multiple items.

    After days of googling, I decided to fool with the UI and stumbled on this myself.. This must be an undocumented “feature.”

    Reply
  7. Thank you Marc for such a good article. It saves my day :)

    There is one thing that I noticed: in line 3, I think there should be something like SP.UI.Status.removeAllStatus(true); instead of RemoveAllStatus(true);
    Other than that, everything else works just fine.

    Reply

Have a thought or opinion?