This post provides an overview of the WorkManagement feature of SharePoint, and how you can use its JSOM API to read and update tasks in SharePoint, Exchange, and Project Server.
Overview
The WorkManagement Service Application in SharePoint 2013 is a powerful but little-understood feature; it aggregates, synchronizes, and updates tasks from across SharePoint, Exchange, and Project Server.
Tasks and Task Lists have been in SharePoint for many versions, but I’ve yet to see any customer really take serious advantage of them. Over the years I’ve written a myriad of “My Tasks” web parts using SharePoint Search or the SharePoint object model to roll up tasks across sites and site collections. While nice, these have always had the typical downsides – very difficult to update tasks across site collections, no single place to create tasks easily, the tradeoffs of using Search (waiting for indexing) or Query (limited to current site collection), and the real kicker – no access to your Outlook/Exchange tasks.
This post will show you how to use the WorkManagement features as an end-user and introduce the API, with a goal of displaying and modifying aggregated tasks in a web part. If you wish, you can skip the introductions and go straight to the code.
How to Use WorkManagement to Manage your Tasks (End User Guide)
Since I still see a lot of confusion from my customers around Task Aggregation, I wanted to start out by showing where to access this feature, how to set it up properly (requires a manual step for each user), and what capabilities it has to manage your tasks (really powerful!).
Where to find My Tasks
You can find your aggregated tasks in your OneDrive for Business (aka MySite, Personal Site, SkyDrive Pro) site collection, at the AllTasks.aspx page, using a URL similar to the following: https://companyname-my.sharepoint.com/personal/username_companyname/AllTasks.aspx. If you don’t have a OneDrive for Business site collection (an admin turned this off, or you are an External User in SharePoint Online), you won’t have access to this feature. If you haven’t mucked with your OneDrive for Business Quick Launch settings, you’ll also see a link to it on the left nav in the MySite Host:
If you’ve read my post explaining the left nav links on the MySite Host, you’ll remember that the Tasks link in the left nav is put there when the personal site is first created. Users can accidentally delete this if they edit their personal site Quick Launch settings. If you want to have them add it back, or if you want to link to this page from somewhere else in your portal, you can use the MySiteRedirect link as well:
https://companyname-my.sharepoint.com/_layouts/15/MySite.aspx?MySiteRedirect=AllTasks
What can you do with it?
The My Tasks page enables you to create personal tasks and see your aggregated tasks. Even though it looks similar, this isn’t your grandma’s ListView web part here. It’s a custom page packed with functionality.
In previous versions of SharePoint, personal tasks were stored in a regular Task List in your personal site. Now there is a hidden list called WmaAggregatorList_User:
This list stores the personal tasks that you create from the My Tasks page, as well as pointers to the tasks that are synced from other systems and SharePoint sites. This means that the personal tasks you create here are not really true SharePoint Task items, and you can’t use search to get at these. More on this in the API discussion later.
On this page is a special set of Ribbon controls for managing tasks and synchronization settings:
I’ll cover most of these buttons in the next sections.
Adding Personal Tasks
To add a task, you can click the add tasks link if you have no tasks yet (first time experience), click the new task button on the page, or click the New Task button on the Tasks Ribbon.
Below I’ve added a couple of personal tasks using the in-line editor:
If you click the New Task button on the ribbon, you get a form to fill out. One cool thing about this form is that you can pick the “Project”, meaning you can choose which Task list in SharePoint this task will be stored in. If you haven’t been assigned any tasks in other SharePoint Task lists, you’ll see the following view without this option:
However, once you have tasks from other Task lists assigned to you, they will sync to the WmaAggregator_User list in your personal site, and you’ll then see the Projects option. Below I’ve added a task in a Project site collection, you can see that it is showing on the My Tasks page, and also that I now have a Projects dropdown to choose from when creating new tasks:
The Projects dropdown (shown expanded in the screen above) is pulled from the list of sites from which you have previously had Tasks assigned, and is not a list of all available Task Lists throughout the SharePoint farm.
Notice the checkbox to Make task public to project members. If this is unchecked, then this task does not get created in the destination site’s Task list. It only gets grouped by that project name on your My Tasks page, and for all intents and purposes it’s a task just for you. If this box is checked, then the Task will get copied to the destination site’s Task list, and the personal task will turn into a Linked task. Once you specify this public option, you can’t go back and make it a personal-only task.
Notifications
Another cool thing is when you are on any of the My Tasks pages in your personal site, if the aggregator sees new tasks, you’ll get a nice Toaster message at the top of the page:
Changing Views, Sorting, and Filtering
The views you have available are:
- Important and Upcoming – Shows a timeline view of upcoming Tasks. You can use the Add to Timeline button on the Tasks ribbon to specifically place tasks on your timeline. This page also rolls up Important (pinned) tasks as well. This page is Highlights.aspx.
- Active – The default view. Shows you tasks that are currently Active. This page is AllTasks,aspx.
- Completed – Shows your completed tasks. This page is Completed.aspx.
- Recently Added – Shows recently added tasks, including Completed tasks. You need to click the ellipsis to see this view. This page is RecentlyAssigned.aspx.
Sorting can be done by clicking the column headers. You can also filter the tasks to show just the personal tasks: click the ellipsis and check the Personal option.
Pinning Tasks (marking as important)
Tasks can be marked as Important; in the API this is referred to as Pinning (or Pinned tasks). When you mark a task as Important, it’ll show up on the Important and Upcoming view, in the Important section. Marking tasks as Important in SharePoint will synchronize with Outlook/Exchange via a Timer Job (and vice versa).
Editing Tasks
You can edit tasks from the My Tasks page, and the edits will synchronize with Exchange Tasks. When you Edit a project task, you’ll be redirected to the Edit form for that task in its respective SharePoint site. When you Edit a personal task, you are displayed a custom Edit form similar to the Create form.
Moving Tasks from Personal to Project
On the edit form for personal tasks, you can choose the Project, meaning you can essentially Move a task from personal to project. As discussed in the section above on creating tasks, you can move a task from personal to project, but not the other way around. Below you can see where I’ve moved my personal task into a project, and it is now showing on the Task list in the other site collection:
Syncing Tasks with Outlook/Exchange
By default, the only tasks that show up in the MyTasks page are the personal Tasks you add, and other SharePoint tasks that are aggregated from SharePoint sites in the same farm as the WorkManagement service application. Tasks you add in Outlook/Exchange will not show on this page until you perform a manual step to synchronize them, which you can do from the Ribbon on the MyTasks page, by clicking on the Sync to Outlook button. You can also do this from the Ribbon on any other Task list in SharePoint or Project Server, and no matter where you do this from, it all has the same effect: it syncs all your SharePoint tasks (both personal and project) with Outlook.
When you click this button, a dialog appears that lets you turn sync on or off:
Once you have this turned on, you’ll then begin to see Exchange-only Tasks show on the My Tasks page, and your personal and project tasks from SharePoint will show in OWA/Outlook. The fantastic part is that it’s a two-way sync, and changes in either side are reflected in moments after a Timer Job runs.
The WorkManagement API
Now for the meat, the programming possibilities. The WorkManagement API is accessible via the SSOM (Microsoft.Office.Server.WorkManagement.dll), via the managed code CSOM client dll (microsoft.sharepoint.workmanagement.client.dll), and via a Javascript JSOM file (/_layouts/15/sp.workmanagement.js).
Unfortunately, the development story here is a little sad. First, there is no REST API. WorkManagement also does not support App Authentication at this time, so this means that for Office365/SharePoint Online, your only viable option for development is to use JSOM outside of an App for SharePoint, for example via a ScriptEditor web part. If you try to use the JSOM or CSOM from an App for SharePoint (either SharePoint-hosted or Provider-hosted), you’ll get a nasty error saying the dll “does not support app authentication”.
Following is a table outlining the API options on the different platforms:
API | Platform | |
---|---|---|
SP On-prem | SP Online | |
SSOM | X | |
CSOM in FTC Web Part or application page | X | |
CSOM in Sandbox Web Part | ||
CSOM in Console App | X | X (w SPO Credentials) |
JSOM in ScriptEditor | X | X |
CSOM or JSOM in SharePoint App |
In the table above I noted the CSOM dll support. This dll is not redistributed with the SharePoint Client Components SDK. You can pull this from an on-prem installation of SharePoint 2013, in the 15/ISAPI folder; however I can’t speak to licensing here, for example if it’s ok to redistribute and use in an Azure Web Job or not.
If you are looking for On-Prem FTC samples, Adis Jugo has a nice sample project that will get you familiar with object model. For the rest of this article, I’ll focus on the JSOM library, and how you can use it to read/write/update tasks.
Reading Tasks
The first example I’ll show is reading the list of personal tasks. For this, I’ll add a ScriptEditor web part to a page, and begin setting up the dependencies. I’m going to use jquery and knockout to help demonstrate the concepts. Here is the first block of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
;" ]<script type="text/javascript" src="/_layouts/15/sp.workmanagement.js"></script> <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.0.min.js"></script> <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/knockout/knockout-3.0.0.js"></script> <script type="text/javascript"> 'use strict'; var context = new SP.ClientContext.get_current(); var userSessionManager = new SP.WorkManagement.OM.UserOrderedSessionManager(context); var userSession = userSessionManager.createSession(); var query = new SP.WorkManagement.OM.TaskQuery(context); var myTasks = userSession.readTasks(query); $(document).ready(function () { getMyTasks(); }); function getMyTasks() { context.load(myTasks); context.executeQueryAsync(onGetMyTasksSuccess, onGetMyTasksFail); } // This function is executed if the above call is successful function onGetMyTasksSuccess() { console.log("Successfully retrieved tasks..."); } // This function is executed if the above call fails function onGetMyTasksFail(sender, args) { console.log('Failed to get tasks. Error:' + args.get_message()); } </script> |
You can see that I’ve included the sp.workmanagement.js file, and done some basic plumbing to get a WorkManagement “session” and to call the readTasks() method on that session. The result returns a TaskClientCollection, which needs to be enumerated to get at the actual task items. The next sample will demonstrate that in the success callback:
1 2 3 4 5 6 7 8 |
function onGetMyTasksSuccess() { console.log("Successfully retrieved tasks..."); var taskEnumerator = myTasks.getEnumerator(); while (taskEnumerator.moveNext()) { var task = taskEnumerator.current; console.log("Task: " + task.get_id() + " - " + task.get_name()); } } |
You can see that I’m accessing some of the properties (actually methods) of the returned Tasks (specifically Id and Name). Below is a table of task-specific properties that are available. These also have corresponding set_xyz() methods, but you can’t use those to update the tasks (more on this later):
Getter | Notes |
---|---|
get_customAttributes | |
get_description | |
get_dueDate | |
get_editUrl | The url to the Edit form for this item. |
get_id | Integer. This is not the actual Id of a task in a task list, it’s the id of the item in the WmaAggregator_User list. |
get_isCompleted | Boolean |
get_isPersonal | Boolean. |
get_isReadOnly | |
get_lastModified | |
get_locationId | The Id of the project. See below on getting a list of locations. |
get_name | The Title of the task. |
get_pinAge | Returns a 1 or 0, indicating if it is marked as important (pinned). |
get_serializedCustomDataForClient | Contains JSON additional data. For example, the task list and original id of the task list item (for SharePoint tasks). |
get_startDate |
After running the above code, you should see a few tasks show up in the console:
The workhorse in all of this is the Session object. Any of the CRUD operations on tasks will go through this object, which has the following methods:
Method | Notes |
---|---|
readTasks | Gets tasks. |
deleteTask | Deletes the task, both personal and project. |
createTask | Creates a personal task. |
createPersonalTaskAndPromoteToProviderTask | Shortcut to creating a project task. |
updateTaskWithLocalizedValue | Updates supported properties on tasks. Not every property can be updated. |
pinTask | Pins a task (marks as important). |
removePinOnTask | Unpins a task. |
promotePersonalTaskToProviderTaskInLocation | This will move a task from personal to a project task. |
Getting Locations
When you call readTasks() to get a list of tasks, the tasks will come back with a locationId; either -1 for personal tasks or an Id that relates to a project location. To get the name/title of this location, you need to make a separate call to get the hydrated list of locations. To do this, I’m going to add a few more lines to the code (see highlighted lines):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
" ]var context = new SP.ClientContext.get_current(); var userSettingsManager = new SP.WorkManagement.OM.UserSettingsManager(context); var locations = userSettingsManager.getAllLocations(); var userSession = new SP.WorkManagement.OM.UserOrderedSessionManager(context).createSession(); var query = new SP.WorkManagement.OM.TaskQuery(context); var myTasks = userSession.readTasks(query); // This code runs when the DOM is ready and creates a context object which is needed to use the SharePoint object model $(document).ready(function () { getMyTasks(); }); function getMyTasks() { context.load(locations); context.load(myTasks); context.executeQueryAsync(onGetMyTasksSuccess, onGetMyTasksFail); } |
Once the call succeeds, you can iterate over the locations using locations.getEnumerator() and .moveNext(), using the same pattern shown earlier for Tasks.
Setting up the ViewModel
The next sections will show you how to perform CRUD operations on the tasks, but before I can demonstrate that, I want to setup a knockout ViewModel to get it ready for databinding operations. To the code I’m going to add a ViewModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var TaskViewModel = function(tasks) { var self = this; self.tasks = ko.observableArray(tasks); self.deleteTask = function(task) { }; self.onDeleteSuccess = function (task) { }; self.togglePin = function(task) { }; self.onPinToggleSuccess = function (task) { }; self.updateTask = function (task) { }; self.onUpdateTaskSuccess = function (task) { }; self.onError = function (sender, args, data) { console.log(args.get_message()); }; }; |
Then I’m going to add a mapping object to represent each task item (you could use the ko.mapping plugin here as well):
1 2 3 4 5 6 |
var TaskItem = function(originalTask) { this.id = ko.observable(originalTask.get_id()); this.name = ko.observable(originalTask.get_name()); this.isPinned = ko.observable(originalTask.get_pinAge() === 0 ? false : true); this.nativeTask = originalTask; }; |
Next, I’m going to wire up the view model and apply the bindings when the call to readTasks()
completes (I’ve highlighted the changed lines):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
" ]function onGetMyTasksSuccess() { console.log("Successfully retrieved tasks..."); var taskEnumerator = myTasks.getEnumerator(); var tasks = []; while (taskEnumerator.moveNext()) { var task = taskEnumerator.current; console.log("Task: " + task.get_id() + " - " + task.get_name()); var taskItem = new TaskItem(task); tasks.push(taskItem); } ko.applyBindings(new TaskViewModel(tasks), $("#taskContainer")[0]); } |
Finally, I’m going to add the HTML markup to the ScriptEditor at the bottom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<div id="taskContainer"> <p>You have <span data-bind="text: tasks().length"></span> Active Tasks.</p> <table data-bind="visible: tasks().length > 0"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Pinned</th> <th /> <th /> <th /> </tr> </thead> <tbody data-bind="foreach: tasks"> <tr> <td><span data-bind="text: id()"></span></td> <td><input class="required" data-bind="value: name, uniqueName: true, valueUpdate: 'afterkeydown'" /></td> <td><input disabled="disabled" type="checkbox" data-bind="checked: isPinned()" /></td> <td><a href="#" data-bind="click: $root.togglePin, text: isPinned() ? 'Unpin' : 'Pin'"></a></td> <td><a href="#" data-bind="click: $root.deleteTask">Delete</a></td> <td><a href="#" data-bind="click: $root.updateTask">Update</a></td> </tr> </tbody> </table> </div> |
Running this now should give the following output:
Pinning Tasks
Now that the plumbing is setup, I’ll fill in the stub methods in the ViewModel with actual code. For pinning/unpinning, you need to determine the current pinned state, and then call either pinTask() or removePinOnTask() on the session, passing in the task id. Remember that these are all async operations, so you need to handle the success and error callbacks:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
self.togglePin = function(task) { var taskWriteResult; if (task.isPinned()) { taskWriteResult = userSession.removePinOnTask(task.id()); } else { taskWriteResult = userSession.pinTask(task.id()); } context.load(taskWriteResult); context.executeQueryAsync( Function.createDelegate(this, function(){self.onPinToggleSuccess(task);}), Function.createDelegate(this, function(sender, args){self.onError(sender, args, taskWriteResult);}) ); }; self.onPinToggleSuccess = function (task) { task.isPinned(!task.isPinned()); }; |
You should be able to click the Pin/Unpin link now and verify that your tasks are indeed toggling importance on your My Tasks page.
Updating Tasks
To update tasks, there are only a handful of properties that support updating, and these can be found in the WritableTaskField enumeration.
- editUrl
- title
- isCompleted
- dueDate
- description
- startDate
To update, you use the updateTaskWithLocalizedValue()
method, provide the task id, the field you wish to update, and the value to update. If you need to update several fields at once, you’ll need to call this multiple times. To update, use the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
self.updateTask = function (task) { var taskWriteResult = userSession.updateTaskWithLocalizedValue(task.id(), SP.WorkManagement.OM.WritableTaskField.title, task.name()); context.load(taskWriteResult); context.executeQueryAsync( Function.createDelegate(this, function(){ console.log("Update Successful!"); self.refreshTask(task); }), Function.createDelegate(this, function(sender, args){ task.name(task.nativeTask.get_name()); self.onError(sender, args, taskWriteResult);}) ); }; self.refreshTask = function (task) { var taskRefreshResult = userSession.refreshSingleTask(task.id()); context.load(taskRefreshResult); context.executeQueryAsync( Function.createDelegate(this, function(){ task.nativeTask(taskRefreshResult.Result);}), Function.createDelegate(this, function(sender, args) {self.onError(sender, args, taskRefreshResult}) ); }; |
Deleting Tasks
Deleting tasks is even less complicated:
1 2 3 4 5 6 7 8 9 10 11 12 |
self.deleteTask = function(task) { var taskWriteResult = userSession.deleteTask(task.id()); context.load(taskWriteResult); context.executeQueryAsync( Function.createDelegate(this, function(){self.onDeleteSuccess(task);}), Function.createDelegate(this, function(sender, args){self.onError(sender, args, taskWriteResult);}) ); }; self.onDeleteSuccess = function (task) { self.tasks.remove(task); }; |
Creating and Moving Tasks
To create new tasks, you use the createTask()
method, and pass in the parameters that define the task (see this for reference). This will create a personal task; but you might notice that one of the parameters is a locationId. If you pass in a valid locationId, you will achieve the same effect as choosing a Project in the UI, but leaving the Public checkbox unchecked. It will still be a personal task, but will be grouped under that project name.
If you want to create a project task straight away, use the createTaskAndPromoteToProvderTask()
method. This will create the task, and turn it into a public task in the project Task list for the locationId specified.
Alternatively, you can move a personal task to a project task later, by calling the promotePersonalTaskToProviderTaskInLocation()
method, and this will move the task to the project task list, and turn the personal task into a linked task.
Since this post is already quite long, I’ll leave the create and move exercises up to you, dear reader.
Summary
Hopefully in this post I’ve shown you how the WorkManagement feature works from an end-user point of view, and how those user interactions can be achieved via the WorkManagement JSOM api.
Pingback: Task aggregation using the REST API on O365 | Martin's Technology Blog
Hey adam,
great article!
I was wondering. Does this code automaticaly update the My Task list as well?
I once ran into a problem that the “My Task” list was not updated when you didn’t visit the “My Tasks” page in your my site. There seems to be a routine on this page (web part) that triggers a sort of syncronisation.
I was reading the hidden list ‘WmaAggregatorList_User’ directly from the My Site. So I was not using the WorkManagement functions that you use in your sample code.
Such a relief to know I now can aggregate all my tasks in the company. I used to have to use a third party app for that, that took for ever to load.
I wonder, is there a way to show all the calenders from the site in newsfeed?
Hello Adam,
Thanks for your great article, very helpful!
I’m trying to use your code on a SharePoint 2013 (office365) but when I call the method userSessionManager.createSession() it seems that something is undefined. userSessionManager is not undefined, but maybe not well initialized.
The code is exactly the same as yours, do you know if it could be some limitations due to office 365?
Thanks, Kind Regards.
Platypus
Hey Adam
Thank you very much for your helpful article! How can I manage to synch also the task status of a task? If I check the task-checkbox as completed, the status in the sharepoint project list is not updatet. Also the other way round doesn’t work…
Thank you and kind regards
Melanie
Hello Adam,
Im getting “TypeError: undefined is not a function” error in the “createSession” line..
I dont know what Im missing..
Thanks..