Using SPServices & jQuery to Find My Stuff from Multi-Select Person/Group Field
- by Mark Rackley
Okay… quick blog post for all you SPServices fans out there. I needed to quickly write a script that would return all the tasks currently assigned to me. I also wanted it to return any task that was assigned to a group I belong to. This can actually be done with a CAML query, so no big deal, right? The rub is that the “assigned to” field is a multi-select person or group field. As far as I know (and I actually know so little) you cannot just write a CAML query to return this information. If you can, please leave a comment below and disregard the rest of this blog post… So… what’s a hacker to do? As always, I break things down to their most simple components (I really love the KISS principle and would get it tattooed on my back if people wouldn’t think it meant “Knights In Satan’s Service”. You really gotta be an old far to get that reference). Here’s what we’re going to do: Get currently logged in user’s name as it is stored in a person field Find all the SharePoint groups the current user belongs to Retrieve a set of assigned tasks from the task list and then find those that are assigned to current user or group current user belongs to Nothing too hairy… So let’s get started Some Caveats before I continue There are some obvious performance implications with this solution as I make a total of four SPServices calls and there’s a lot of looping going on. Also, the CAML query in this blog has NOT been optimized. If you move forward with this code, tweak it so that it returns a further subset of data or you will see horrible performance if you have a few hundred entries in your task list. Add a date range to the CAML or something. Find some way to limit the results as much as possible. Lastly, if you DO have a better solution, I would like you to share. Iron sharpens iron and all… Alright, let’s really get started. Get currently logged in user’s name as it is stored in a person field First thing we need to do is understand how a person group looks when you look at the XML returned from a SharePoint Web Service call. It turns out it’s stored like any other multi select item in SharePoint which is <id>;#<value> and when you assign a person to that field the <value> equals the person’s name “Mark Rackley” in my case. This is for Windows Authentication, I would expect this to be different in FBA, but I’m not using FBA. If you want to know what it looks like with FBA you can use the code in this blog and strategically place an alert to see the value. Anyway… I need to find the name of the user who is currently logged in as it is stored in the person field. This turns out to be one SPServices call: var userName = $().SPServices.SPGetCurrentUser({ fieldName: "Title", debug: false }); As you can see, the “Title” field has the information we need. I suspect (although again, I haven’t tried) that the Title field also contains the user’s name as we need it if I was using FBA. Okay… last thing we need to do is store our users name in an array for processing later: myGroups = new Array(); myGroups.push(userName); Find all the SharePoint groups the current user belongs to Now for the groups. How are groups returned in that XML stream? Same as the person <ID>;#<Group Name>, and if it’s a mutli select it’s all returned in one big long string “<ID>;#<Group Name>;#<ID>;#<Group Name>;#<ID>;#<Group Name>;#<ID>;#<Group Name>;#<ID>;#<Group Name>”. So, how do we find all the groups the current user belongs to? This is also a simple SPServices call. Using the “GetGroupCollectionFromUser” operation we can find all the groups a user belongs to. So, let’s execute this method and store all our groups. $().SPServices({ operation: "GetGroupCollectionFromUser", userLoginName: $().SPServices.SPGetCurrentUser(), async: false, completefunc: function(xData, Status) { $(xData.responseXML).find("[nodeName=Group]").each(function() { myGroups.push($(this).attr("Name")); }); } }); So, all we did in the above code was execute the “GetGroupCollectionFromUser” operation and look for the each “Group” node (row) and store the name for each group in our array that we put the user’s name in previously (myGroups). Now we have an array that contains the current user’s name as it will appear in the person field XML and all the groups the current user belongs to. The Rest Now comes the easy part for all of you familiar with SPServices. We are going to retrieve our tasks from the Task list using “GetListItems” and look at each entry to see if it belongs to this person. If it does belong to this person we are going to store it for later processing. That code looks something like this: // get list of assigned tasks that aren't closed... *modify the CAML to perform better!* $().SPServices({ operation: "GetListItems", async: false, listName: "Tasks", CAMLViewFields: "<ViewFields>" + "<FieldRef Name='AssignedTo' />" + "<FieldRef Name='Title' />" + "<FieldRef Name='StartDate' />" + "<FieldRef Name='EndDate' />" + "<FieldRef Name='Status' />" + "</ViewFields>", CAMLQuery: "<Query><Where><And><IsNotNull><FieldRef Name='AssignedTo'/></IsNotNull><Neq><FieldRef Name='Status'/><Value Type='Text'>Completed</Value></Neq></And></Where></Query>", completefunc: function (xData, Status) { var aDataSet = new Array(); //loop through each returned Task $(xData.responseXML).find("[nodeName=z:row]").each(function() { //store the multi-select string of who task is assigned to var assignedToString = $(this).attr("ows_AssignedTo"); found = false; //loop through the persons name and all the groups they belong to for(var i=0; i<myGroups.length; i++) { //if the person's name or group exists in the assigned To string //then the task is assigned to them if (assignedToString.indexOf(myGroups[i]) >= 0){ found = true; break; } } //if the Task belongs to this person then store or display it //(I'm storing it in an array) if (found){ var thisName = $(this).attr("ows_Title"); var thisStartDate = $(this).attr("ows_StartDate"); var thisEndDate = $(this).attr("ows_EndDate"); var thisStatus = $(this).attr("ows_Status"); var aDataRow=new Array( thisName, thisStartDate, thisEndDate, thisStatus); aDataSet.push(aDataRow); } }); SomeFunctionToDisplayData(aDataSet); } }); Some notes on why I did certain things and additional caveats. You will notice in my code that I’m doing an AssignedToString.indexOf(GroupName) to see if the task belongs to the person. This could possibly return bad results if you have SharePoint Group names that are named in such a way that the “IndexOf” returns a false positive. For example if you have a Group called “My Users” and a group called “My Users – SuperUsers” then if a user belonged to “My Users” it would return a false positive on executing “My Users – SuperUsers”.IndexOf(“My Users”). Make sense? Just be aware of this when naming groups, we don’t have this problem. This is where also some fine-tuning can probably be done by those smarter than me. This is a pretty inefficient method to determine if a task belongs to a user, I mean what if a user belongs to 20 groups? That’s a LOT of looping. See all the opportunities I give you guys to do something fun?? Also, why am I storing my values in an array instead of just writing them out to a Div? Well.. I want to pass my data to a jQuery library to format it all nice and pretty and an Array is a great way to do that. When all is said and done and we put all the code together it looks like: $(document).ready(function() { var userName = $().SPServices.SPGetCurrentUser({ fieldName: "Title", debug: false }); myGroups = new Array(); myGroups.push(userName ); $().SPServices({ operation: "GetGroupCollectionFromUser", userLoginName: $().SPServices.SPGetCurrentUser(), async: false, completefunc: function(xData, Status) { $(xData.responseXML).find("[nodeName=Group]").each(function() { myGroups.push($(this).attr("Name")); }); // get list of assigned tasks that aren't closed... *modify this CAML to perform better!* $().SPServices({ operation: "GetListItems", async: false, listName: "Tasks", CAMLViewFields: "<ViewFields>" + "<FieldRef Name='AssignedTo' />" + "<FieldRef Name='Title' />" + "<FieldRef Name='StartDate' />" + "<FieldRef Name='EndDate' />" + "<FieldRef Name='Status' />" + "</ViewFields>", CAMLQuery: "<Query><Where><And><IsNotNull><FieldRef Name='AssignedTo'/></IsNotNull><Neq><FieldRef Name='Status'/><Value Type='Text'>Completed</Value></Neq></And></Where></Query>", completefunc: function (xData, Status) { var aDataSet = new Array(); //loop through each returned Task $(xData.responseXML).find("[nodeName=z:row]").each(function() { //store the multi-select string of who task is assigned to var assignedToString = $(this).attr("ows_AssignedTo"); found = false; //loop through the persons name and all the groups they belong to for(var i=0; i<myGroups.length; i++) { //if the person's name or group exists in the assigned To string //then the task is assigned to them if (assignedToString.indexOf(myGroups[i]) >= 0){ found = true; break; } } //if the Task belongs to this person then store or display it //(I'm storing it in an array) if (found){ var thisName = $(this).attr("ows_Title"); var thisStartDate = $(this).attr("ows_StartDate"); var thisEndDate = $(this).attr("ows_EndDate"); var thisStatus = $(this).attr("ows_Status"); var aDataRow=new Array( thisName, thisStartDate, thisEndDate, thisStatus); aDataSet.push(aDataRow); } }); SomeFunctionToDisplayData(aDataSet); } }); } }); }); Final Thoughts So, there you have it. Take it and run with it. Make it something cool (and tell me how you did it). Another possible way to improve performance in this scenario is to use a DVWP to display the tasks and use jQuery and the “myGroups” array from this blog post to hide all those rows that don’t belong to the current user. I haven’t tried it, but it does move some of the processing off to the server (generating the view) so it may perform better. As always, thanks for stopping by… hope you have a Merry Christmas…