Display All Related Tasks for a SharePoint Workflow Using jQuery, SPServices, and jQueryUI

This was a fun one to build. A client wanted to be able to see all of the related tasks for a workflow without having to do the two or more clicks it can take to get there using a standard SharePoint list view. By layering jQuery, SPServices, and jQueryUI, we were able to display the information in a nicely formatted dialog with only one click, and make it a richer experience, to boot!

Using Christophe Humbert’s (@Path2SharePoint) excellent Easy Tabs, each user had their own customized dashboard which showed their current workload. This dashboard was part of a rich user experience which was really a one-stop shop for each user to accomplish all of their main SharePoint tasks. Each tab had a straightforward view of a Document Library which showed the current status of the workflow for each document., as shown below.

image

The column on the far right is a status column for the Customer Request workflow. As you can see, while the regular text link to details about the workflow is available, there’s also a small question mark next to the documents where the workflow is still running.

When the user clicks on the question mark for any document, they get a concise view of the task details for that workflow instance:

image

I built the script so that it will show all of the tasks for the document if there is more than one:

image

If you think through the normal steps to see those details, it’s at least two or three clicks to get to the workflow’s tasks, and the two or three to get back where you were. This was the goal: to provide a single click way to understand the current workflow status. (We considered showing the details on the hover event, but it seemed as though that would be a bit too obtrusive.)

So, how did all of this work? Well first I added the appropriate references to the page. In this case, I added the following lines below the line:

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<script language="javascript" type="text/javascript" src="/Scripts/jquery-1.5.min.js"></script>
<script language="javascript" type="text/javascript" src="/Scripts/jquery.SPServices-0.6.1.min.js"></script>
<script language="javascript" type="text/javascript" src="/Scripts/jquery-ui-1.8.12.custom.min.js"></script>
<script language="javascript" type="text/javascript" src="/Scripts/Workflow.js"></script>
<link rel="stylesheet" href="/Scripts/css/jquery-ui-1.8.12.custom.css"/>
<link rel="stylesheet" href="/Scripts/css/Workflow.css"/>

We needed the jQuery library, my SPServices library, the jQueryUI library, my custom script for the page, the jQueryUI CSS for the selected theme, and my custom CSS.

The custom CSS ended being pretty insignificant; it just made a couple of small changes to one class.

.cm-app-info {
    display:inline-block;
    cursor:pointer;
}

The meat of things was Workflow.js. In that script file, I did all the work to make the modal dialog happen. I’ve added comments throughout the script, so hopefully it will make decent sense. I’ve cut out a few extraneous bits to simplify things, so I hope that I haven’t introduced any errors. In any case, you should always consider code samples in my blog (and most others) simply as a starting point for doing your own thing.

$(document).ready(function() {

 // In this section, we find the important elements in the DOM for later use
 // --> Note that the specifics in the selectors will be different in your situation, but you can use this as a model
 var customerContractsTable = $("table#onetidDoclibViewTbl0[summary='Customer Contracts ']");
 var customerContracts = $("table#onetidDoclibViewTbl0[summary='Customer Contracts '] > tbody > tr:gt(0)");

 // Add a div to hold the text for the modal dialog
 $(customerContractsTable).after("<div id='workflowDetailsText'></div>");
 var workflowDetailsText = $("#workflowDetailsText");

 // Find each Customer Requests cell (the seventh column, which with zero-indexing is in position 6) where the current status = "2" (In Progress)
 $(customerContracts).find("td:eq(6) span[value='2']").each(function() {
  // For each, show the question mark icon from jQueryUI by appending a span with the appropriate CSS classes
  $(this).closest("td").append("<span class='cm-app-info ui-icon ui-icon-help' ></span>");
 });

 // When the user clicks on the question mark icon...
 $(".cm-app-info").click(function () {

  // Get the link to the current item (servername hard coded for simplicity - not best practice)
  var thisItem = "https://servername" + $(this).closest("tr").find("td.ms-vb-title a").attr("href");

  // Call the Workflow Web Service (GetWorkflowDataForItem operation) to get the currently running workflows for this item
  $().SPServices({
   operation: "GetWorkflowDataForItem",
   item: thisItem,
   async: false, // We'll do this asynchronously
   completefunc: function (xData, Status) {

    $(xData.responseXML).find("ActiveWorkflowsData > Workflows > Workflow").each(function() {
     var thisStatus = $(this).attr("Status1");

     // Only show workflows which are currently In Progress (Status1 = 2)
     if(thisStatus == "2") {
      var thisTaskListId = $(this).attr("TaskListId");
      var thisTemplateId = $(this).attr("TemplateId");

      // With the information we've just gotten for the workflow instance, get the relevent task items in the Workflow Tasks List
      $().SPServices({
       operation: "GetListItems",
       listName: thisTaskListId,
       async: false, // We'll do this asynchronously
       completefunc: function (xData, Status) {

        // Initiation
        $(workflowDetailsText).html("");
        var out = "<table>";
        var taskNum = 0;

        // Get information for each of the task items
        $(xData.responseXML).find("[nodeName='z:row']").each(function() {
         if($(this).attr("ows_WorkflowLink").indexOf(thisItem) > -1) {

          taskNum++;

          // Format Assigned To
          var assignedTo = $(this).attr("ows_AssignedTo");
          assignedTo = "<a href='/_layouts/userdisp.aspx?ID=" + assignedTo.substring(0, assignedTo.search(";#")) +
           "&Source=" + location.href + "'>" +
           assignedTo.substring(assignedTo.search(";#") + 2) + "</a>";

          // Format Due Date
          var dueDate = $(this).attr("ows_DueDate");
          dueDate =
           dueDate.substr(5, 2) + "/" + // month
           dueDate.substr(8, 2) + "/" + // day
           dueDate.substr(0, 4); // year

          // Format Percent Complete
          var percentComplete = 100 * parseFloat($(this).attr("ows_PercentComplete")).toFixed(0);

          // Add the information to the dialog
          out += "<tr><td>Task #" + taskNum + ":</td><td>" + $(this).attr("ows_Title") + "</td></tr>" +
           "<tr><td>Assigned to:</td><td>" + assignedTo + "</td></tr>" +
           "<tr><td>Priority:</td><td>" + $(this).attr("ows_Priority") + "</td></tr>" +
           "<tr><td>Complete:</td><td>" + percentComplete + "%</td></tr>" +
           "<tr><td>Due:</td><td>" + dueDate + "</td></tr>" +
           "<tr><td colspan='99'><hr/></td></tr>";
         };
        });
        out += "</table>";

        // Add the assembled markup to the container we built for it
        $(workflowDetailsText).html(out);

        // Show the dialog using jQueryUI's .dialog() function
        $(workflowDetailsText).dialog({
         open: true,
         title: "Open Tasks",
         minHeight: 500,
         minWidth: 500,
         modal: true,
         buttons: {
          OK: function() {
           $(this).dialog( "close" );
          }
         }
        });
       }
      });
     }
    });
   }
  });
 });
});

I think that this is a really nice use of scripting to enhance the user experience. Not only does the page start to feel more like a “modern” Web page, but we significantly reduced the number of clicks the user would need to do to accomplish their task. Truth be told, even if they did know what click route to follow, the display of the workflow status information is fixed and not all that intuitive. By using this scripting approach, we can easily change how we display the information in the modal dialog. One enhancement we considered was to grab the photo from the User Profile for the person who currently owned the workflow task. This would be a nice little add on.

One other thing worth noting: because the script is built to do the Web Services calls only if the user clicks on one of the icons, there’s little overhead in those instances where the user doesn’t choose to click. There’s no postback, and only the information from the list items which are required to display the full status is requested.

Similar Posts

23 Comments

  1. Marc,

    This is a cool solution. The only feedback I might offer would be to leverage the out of the box Javascript API for modal dialogs. This would negate the need for the additional jQuery UI library, and provide a consistent user experience between your solution and other dialog operations within SharePoint.

    1. Mike:

      That’s a fair point. A couple of reasons I went with the jQueryUI dialogs:
      * I find the SharePoint client-side script to be more bloated and difficult to use. It’s clearly built with .NET developers in mind and follows the more verbose type of syntax they are used to rather than what Web developers might be more used to.
      * We used jQueryUI for some other things as well, so we already had it loaded and available. What I outline here is only one part of the overall solution.

      M.

  2. Hey Marc, Great stuff here…. I am having a hard time trying to do the following … I have a site template that I want to use it has a site workflow but everytime I setup the site I am always having to change the templateid guid of the workflow which is called in my jQuery code. What I would like to be able to do is call spservices to get the site workflow id by the workflow name. Is there a way to do this. So far I can only see gettemplatefor item …. Thanks for your help

    1. Mike:

      GetTemplateForItem seems to be the only operation that might help. I suppose you could have one item in a list already in your template and then check what workflows are available for that item. Then you could delete the item (or list) with SPServices as well.

      Oddly, in trying to find the Workflow Web Service documentation for SharePoint 2010 in MSDN, there doesn’t seem to be anything. However, the operations available at /_vti_bin/workflow.asmx in 2010 seem to be the same as the ones I point to from the Workflow documentation page here in SPServices.

      M.

      1. Hi Marc, Thanks for the reply on the GetTemplatesForItems. Yeah I tried that with referencing a list or document library that has one item in it. The responseXML for that was that the was empty since there are no associated workflows for that list or Document Library…. I even tried to point the method to the Workflow.aspx or the ListData.svc to get a list of the workflows… No luck.

        I was thinking that maybe I need to examine the construction of the soap call to see if there is a way to get the WF template id’s enumerated. Once I can get that maybe I could create a function call for that …

        The one thing I haven’t tried is to force the workflow to run on site creation and then read the template ID from the Workflow History list after it fails but that would be a little extreme. ;)

  3. Hi Marc,
    Firstly I wanted to say this is a great way to improve the users’ experience with SharePoint. I have heard from a lot of clients about how cumbersome it is to track tasks created by SharePoint workflow.

    I do have one question. It seems you declared a variable named thisTemplateId, but you never used it in your code. Is there any particular reason you declared it? Thanks!

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.