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
[email protected]
@adiazcan
http://geeks.ms/blogs/adiazmartin

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

Similar Posts

56 Comments

  1. Hi. Thank you for this post. Although it took some time getting it to work, I now have it working perfectly! :)

    Two main problems I had were firstly passing in the ListId parameter from the SharePoint Action, I just couldn’t work out how to do that, secondly an typo on line 10. Both issues fixed by removing the input parameter and the following lines of code for lines 9 & 10:

    var myListId = SP.ListOperation.Selection.getSelectedList();
    var list = web.get_lists().getById(myListId);
    var items = SP.ListOperation.Selection.getSelectedItems(clientContext);

    Thank you once again :)

  2. Alberto and Marc, a side question if you don’t mind: how many workflows do you think could be triggered with such code? I’d be concerned that if you run it on too many items you are going to reach a threshold, and workflow calls will be delayed or even worse cancelled.

    1. Thanks for the reply and the link Alberto! I was expecting something like this, so in the solution I wrote, I included a 2 second timeout between workflow triggers (based on your reply I could probably make it a 0.2 second timeout).

  3. I have used the GetTemplatesForItem extensivly, however Users are not getting any results back – unless I give them design access to the site – this isn’t possible. do you know what permissions specifically I would need to grant the users?

  4. Hi Marc,

    Thanks for this great post and help provided to us, I am facing a issue here, my code is not at all entering into SPServices () function though I gave correct path of js files.

    Challenge : need to create a workflow that will create new row with selected Item and allow to edit latest row,
    Solution : created a workflow for this functionality and it is working as expected when I start it manually.

    My second challenge is to run this workflow dynamically so we can skip the start workflow page.

    My Environment : Office 365 ( SP 2013 ) , SPD 2013

    Code

    $().SPServices({
    operation: “GetTemplatesForItem”,
    item: “Server/sites/Test_Environment/Test_BreadCrum/Lists/Test_List/” + SelectedID + “_.000”,
    async: true,
    completefunc: function (xData, Status) {
    var currentItemURL = this.item;
    $(xData.responseXML).find(“WorkflowTemplates > WorkflowTemplate”).each(function (i, e) {
    if ($(this).attr(“Name”) == “Copy”) {
    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
    alert(workflowGUID);
    }
    }
    }
    }
    })

    $().SPServices({
    operation: “StartWorkflow”,
    item: “Server/sites/Test_Environment/Test_BreadCrum/Lists/Test_List/” + SelectedID + “_.000”,
    templateId: “{8b65eb82-2890-486b-b63c-bee77c1c1779}”,
    workflowParameters: “”,
    async: true,
    completefunc: function () {
    SP.UI.Notify.addNotification(“Workflow process started on selected item.”, false);
    }
    });

    Could you please help me on this.

  5. Hello,

    Is it possible to use this example to StartWorkflow on multiple selected FILES in Library?

    I tried to modify the code but without success.
    Please help.

    Regards,
    Wojtek

  6. Hi Mark, i keep receiving the below when trying to run the script.

    ‘currentItemURL is not defined’

    I have tried numerous combinations of JQuery and SPServices, but cannot find any to recognise the above.

    What versions are you using?

    Thanks,

    Rich

  7. Hey I found a workaround to all this coding.

    First, set your workflow to run every time a item is changed.

    Secondly, add a temporary text type column to the list that you want to run multiple updates on.

    Thirdly, in Internet Explorer switch to a datasheet view and make sure to include the new field you just created to the data field view.

    Fourthly, go to the first item in your list and enter any string into the new temporary field you just created. Before you commit the changes, highlight the whole column, right click and select fill down. Then change the view back to the standard view (SharePoint will alert you that changes are being made to the list so you should stick around). The workflow will run for all the changes you just made.

    All set you have updated multiple items with the results pushed by your workflow.

    1. Darth:

      Your suggestion may work in some use cases, but I think Alberto’s is more robust. With your approach, you’re actually altering the data in each item to fire off the workflow. This will cauue the values for Modified and Modified By to change. In many cases where there is a workflow in place, those values are important to the workflow itself.

      M.

  8. All,

    Where is the best place to put the delay? I have a workflow that is creating a document set if one is not already created with a specific name provided in a column. Then updating the document path to add the document to that document set. If there is a document set already, it will update the document’s path to add it to the existing document set.

    The issue is, I use this code to process multiple documents at the same time. They all try to create the document set at the same time and the workflow errors out. I need to put a few second pause after the first workflow is run so the first workflow has time to complete prior to the second one starting.

    Note: I’m new to JavaScript so any additional resources would be helpful.

  9. Hi! I have question, how to execute the JS file in the button ?? I can’t reply the example .
    I create one actionCustom Botton on sharepoint designer 2013 , by the other hand I don’t know where locate the library of spServices and the file with the function of the post

    1. Leandro:

      You can store the .js files wherever you want; a Document Library is probably the easiest place. Then you could add references to the script files in a Content Editor Web Part or using some other method.

      M.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.