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.
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:
I built the script so that it will show all of the tasks for the document if there is more than one:
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.