Sunday, May 22, 2011

SharePoint 2010: Approve/Reject Multiple Items

You can approve/Reject an item from SharePoint ribbon. But only one item can be approved or rejected. But I’ve found requirements from few of my clients that they want to approve/reject in batch rather than one by one. This is logical. If there’s 100 of items to approve/reject, doing this one by one is tedious. In this post I’ve described  how I’ve implemented the idea and at the end of the blog you can find the link to download the source code.

 

My approach to allow multiple approve/reject in batch is following the steps:

  • Add a new ribbon “Approve/Reject Selection” as shown below. The new ribbon will be active when more than one item will be selected.
    image
    Figure 1: New ribbon “Approve/Reject Selection” added

  • When multiple item will be selected from grid the “Approve/Reject Selection” will be active as shown below:
    image
    Figure 2: “Approve/Reject Selection” will be active when multiple items will be selected

  • Clicking on “Approve/Reject Selection” will bring up a new custom window developed my me as shown below:
    image
    Figure 3: Approve/Reject Multiple items dialog

  • Finally, the custom dialog shown in figure 3, is an application page where we need to write code to approve/reject selected items programmatically.


So let’s start with the process of implementing the idea!!!!!!!!!!!

 

Step 1: Create a custom ribbon

First add a new empty element as shown below:

image

Figure 4: Add new empty element

 

Then add the following xml in the elements.xml file:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="COB.SharePoint.Ribbon.NewControlInExistingGroup"
Location="CommandUI.Ribbon.ListView"
RegistrationType="List"
RegistrationId="100">
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition Location="Ribbon.ListItem.Workflow.Controls._children">
<Button Id="COB.SharePoint.Ribbon.NewControlInExistingGroup.Notify"
Command="COB.Command.NewControlInExistingGroup.Notify"
Sequence="21"
Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png"
Image16by16Top="-48" Image16by16Left="-240"
Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png"
Image32by32Top="-448" Image32by32Left="-384"
Description="Uses the notification area to display a message."
LabelText="Approve/Reject Selection"
TemplateAlias="o1"/>
</CommandUIDefinition>
</CommandUIDefinitions>
<CommandUIHandlers>
<CommandUIHandler
Command="COB.Command.NewControlInExistingGroup.Notify"
EnabledScript="javascript:enableApprovalAll();"
CommandAction="javascript: showApproveAll(); "/>
</CommandUIHandlers>
</CommandUIExtension>
</CustomAction>
<CustomAction
Id="COB.Command.NewControlInExistingGroup.Notify.Script"
Location="ScriptLink"
ScriptSrc ="/_layouts/SharePoint.ApproveRejectTest/Scripts/ApproveReject.js"/>
</Elements>

Figure 5: Code snippet for Custom Ribbon

I’m not going to describe the code snippet at figure 5 elaborately. However the basic things are that, I’m adding a button with label “Approve/Reject Selection” and I’ve associated two commands with the button. One is when to enable/disable the button with CommandUIHandler’s EnableScript attribute. The buttton click event action is defined with CommandAction attribute. If you notice I’ve just mentioned two javascript function enableApproveAll and showApproveAll. These two functions are not defined in this xml. Rather they are defined in another file “/_layouts/SharePoint.ApproveRejectTest/Scripts/ApproveReject.js” which is referenced in xml file.

 

Step 2: Create the script file to show/hide approve/reject dialog

The content of the  ApproveReject.js file is show below. The command to show the approve/reject dialog is declared in this script. The function showApproveAll() will show a custom application page that I’ve described in step 3. The reference section in the file helps to get intellisense. I’ve not explained the script that much as it’s not in the scope of this post.

/// <reference path="/_layouts/MicrosoftAjax.js"/>
/// <reference path="/_layouts/SP.debug.js"/>
/// <reference path="/_layouts/SP.Core.debug.js"/>
/// <reference path="/_layouts/SP.Ribbon.debug.js"/>
/// <reference path="_layouts/SP.UI.Dialog.debug.js"/>

/// <reference path="/_layouts/actionmenu.js" />
/// <reference path="/_layouts/ajaxtoolkit.js" />
/// <reference path="/_layouts/CUI.debug.js" />
/// <reference path="/_layouts/portal.js" />
/// <reference path="/_layouts/SP.Exp.debug.js" />
/// <reference path="/_layouts/SP.Runtime.debug.js" />
/// <reference path="/_layouts/SP.UI.Dialog.debug.js" />

//used to show approve/reject dialog
function showApproveAll() {
var ctx = new SP.ClientContext.get_current();
var ItemIds = "";
//get current list id
var listId = SP.ListOperation.Selection.getSelectedList();
//get all selected list items
var selectedItems = SP.ListOperation.Selection.getSelectedItems(ctx);

//collect selected item ids
for (var i = 0; i < selectedItems.length; i++) {
ItemIds += selectedItems[i].id + ",";
}

//prepare cutom approval page with listid
//and selected item ids passed in querystring
var pageUrl = SP.Utilities.Utility.getLayoutsPageUrl(
'/SharePoint.ApproveRejectTest/ApproveAll.aspx?ids=' + ItemIds + '&listid=' + listId);
var options = SP.UI.$create_DialogOptions();
options.width = 420;
options.height = 250;
options.url = pageUrl;
options.dialogReturnValueCallback = Function.createDelegate(null, OnDialogClose);
SP.UI.ModalDialog.showModalDialog(options);
}

//used to determine whether the 'approve/reject selection'
//ribbon will be enalbed or disabled
function enableApprovalAll() {
var ctx = new SP.ClientContext.get_current();
return SP.ListOperation.Selection.getSelectedItems(ctx).length > 1;
}


//called on dialog closed
function OnDialogClose(result, target) {
//if ok button is clicked in dialog, reload the grid.
if (result == SP.UI.DialogResult.OK) {
location.reload(true);
}
}

 
Figure 6: ApproveReject.js  file
 
In the method showApproveAll, I’ve collected the selected Items’ IDs and passed to “ApproveAll.aspx” page as querystring. I’ve also passed the current list id in querystring.
 
 

Step 3: Create custom Approve/Reject application page

Finally I’ve developed a application page named as “ApproveAll.aspx”. The partial markup of the page is shown below:

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
<script type="text/javascript">
   1:  
   2:         function closeDialog() {
   3:             SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.cancel, 'Cancelled clicked');
   4:         }
   5:         function finisheDialog() {
   6:             SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.OK, 'Cancelled clicked');
   7:         }
   8:     
</script>
<h2 id="divMessage" runat="server">
</h2>
<table>
<tr>
<td>
Status:
</td>
<td>
<asp:DropDownList ID="ddlAprovalOptions" runat="server">
<asp:ListItem Text="Approve" Value="Approved" />
<asp:ListItem Text="Pending" Value="Pending" />
<asp:ListItem Text="Reject" Value="Denied" />
</asp:DropDownList>
</td>
</tr>
<tr>
<td>
Comments:
</td>
<td>
<asp:TextBox ID="txtComments" runat="server" TextMode="MultiLine" Columns="40" Rows="5" MaxLength="255" />
</td>
</tr>
<tr>
<td>
</td>
<td>
<asp:Button ID="btnSubmit" runat="server" Text="OK" OnClick="btnOk_Click" />
<input type="button" runat="server" id="btnCancel" value="Cancel" onclick="closeDialog()" />
</td>
</tr>
</table>
</asp:Content>

Figure 7: ApproveAll.aspx page’s markup

In the code behind of the page, you need to extract the list id and list item ids. Then you need to invoke a method like shown below to approve/reject items:

private void ApproveRejectItems(SPWeb web, string listId, SPModerationStatusType moderationStatusType, List<int> itemIDs)
{
web.AllowUnsafeUpdates = true;
SPList spList = web.Lists[new Guid(listId)];
foreach (var itemId in itemIDs)
{
SPListItem spListItem = spList.GetItemById(itemId);

//disable workflow
foreach (SPWorkflow workflow in spListItem.Workflows)
{
if (workflow.ParentAssociation.Id == spList.DefaultContentApprovalWorkflowId)
{
SPWorkflowManager.CancelWorkflow(workflow);
}
}

//update moderation status
spListItem.ModerationInformation.Comment = txtComments.Text;
spListItem.ModerationInformation.Status = moderationStatusType;
spListItem.Update();
}
}
Figure 8: Approve/Reject items

As shown in the code snippet in figure 8, first we need to make sure we disable content approval workflow, if exists. Then we can update the moderation status.

 

Download Source Code

You can download the code for this post from my MSDN code gallery http://archive.msdn.microsoft.com/SP2010ApproRejectExt. Then from download tab download the first file “SharePoint.ApproveRejectTest”.

41 comments:

  1. Excellent post. Approving and Rejecting multiple items from a list is definitely a very useful option to have.

    ReplyDelete
  2. Not work in IE9

    ReplyDelete
  3. The code is not just download-and-deploy mode. This is just a piece of how you can implement. You may need to fine tuning the code as per your needs. But I've tried to give the gist of how this can be done.

    ReplyDelete
    Replies
    1. Hi Sohel, I've updated my solution based on your work here: http://ianankers.wordpress.com/2012/08/03/updated-approve-or-reject-multiple-items-in-a-sharepoint-2010-list-5/

      Cheers

      Ian

      Delete
    2. Thanks Ian for your contribution.

      Delete
  4. Hello Sohel,
    I am getting error at
    CommandAction="javascript: showApproveAll(); "/>

    its unable to find the object

    ReplyDelete
  5. @bhum, make sure you have the script file '/_layouts/SharePoint.ApproveRejectTest/Scripts/ApproveReject.js' in place. This is the script file where the js methods are defined. if you rename or copy my code please update the js file reference in elements.xml file

    ReplyDelete
  6. Great help Thank you!

    Im having some trouble to implement this solution in my farm is there alot of changes you have to do to make it work!?

    ReplyDelete
  7. i deploy the solution but it doesn´t change anything! =S

    ReplyDelete
  8. Please make sure you have activated the feature.

    ReplyDelete
  9. it is activated but there is no change in the ribbon! i can see that the code adds buttons to the ribbon but i cant see that change in the site.

    i will appriciate any help

    ReplyDelete
  10. Hi,
    Sometimes the ribbon update doesn't work due to caching. Please check my another post on this: http://ranaictiu-technicalblog.blogspot.com/2011/08/sharepoint-2010-redeploying-ribbon.html

    ReplyDelete
  11. Great post Sohel,

    im facing problem with showing custom action ribbon, because my list is customly add by import webpart list to sharepoint page..

    but the idea is great, finding another approach yet still using your application page..
    and its working..

    thanks sohel, this post save my hours.. :)

    ReplyDelete
  12. Hi Sohel, great information. For those people who don't have the knowledge to built on your concept, I've created a ready to go solution which uses the standard SharePoint resources which can be found here: http://ianankers.wordpress.com/2011/09/29/approve-or-reject-multiple-items-in-a-sharepoint-2010-list/

    One recommendation I would like to make, is to use SP.UI.ModalDialog.RefreshPage instead of location.reload.

    Take care, Ian.

    ReplyDelete
    Replies
    1. Great post,Ian please can you tell me how the deploy these files in Sharepoint 2010

      Delete
  13. Sohel,
    It's good post, but after deploying your solution it is not working.i have deployed the code as it is, i just want to make sure that multiselect is working. In ribbon also it is not showing up the "Approve/Reject Selection". Am i missing something here ?, infact i have refreshed the ribbon.

    As well as in your solution i have recreate a new class "CAApproveRejectSection" and create the same values. Otherwise it is showing the error/warning icon on the file.

    ReplyDelete
  14. Here is the error i getting:

    Warning 1 Cannot initialize the following SharePoint project item: 'CAApproveRejectSelection'. This item requires a type provider that has the following ID, but this provider could not be found: 'CKS.Dev.SharePoint.CustomAction'. Reinstall the extension that provides this item type, or remove the item from your project.
    0 0 SharePoint.ApproveRejectTestWithVisual

    ReplyDelete
  15. @helloworld, Have you activated the feature related to ribbon? You need to activate the feature. Sometimes activating the feature or reinstalling the solution doesn't update ribbon. Please take a look at my other post to get help in this: http://ranaictiu-technicalblog.blogspot.com/2011/08/sharepoint-2010-redeploying-ribbon.html

    From the error description, I think you need CKS extension for Visual Studio to be installed.

    ReplyDelete
  16. Thanks Sohel, just what I needing, well almost, I updated it so the solution could be used on list items too.

    See my blog for details

    ReplyDelete
  17. Thanks Sohel,Its very good post. Its working fine.Default AllItems.aspx page for List is working fine in a ribbon. but when i create new page in that page i added a List View Webpart. In that page when i select a multiple items in a List But in a ribbon the icon is not coming "Approve/Reject Selection". Is there any solution for getting in our custom page also....If you know plz share with me....

    ReplyDelete
  18. @Balaji, You need to change the ribbon location for the code shown in figure 4. Currently the location is 'CommandUI.Ribbon.ListView', you need to change the location or you need to make the ribbon visible from server side code.

    ReplyDelete
  19. Thanks Sohel, Is there anyway to set maximize width and height for the approval popup window.And also how to customize the builtin approval popup window for single item selection.

    ReplyDelete
  20. HI Sohel, How to remove builtin Approve/Reject Button on ribbon

    ReplyDelete
  21. HI Sohel, How to remove builtin Approve/Reject Button on ribbon

    ReplyDelete
  22. @Vamsi, I think the following two links might be helpful:
    http://sharepointegg.blogspot.com/2010/02/remove-button-from-ribbon-in-sharepoint.html
    http://msdn.microsoft.com/en-us/library/ee537543.aspx
    You can also google to find out resources related to remove/hide ribbon button.

    ReplyDelete
  23. I have a quetion in sequential workflow in visual studio 2010. There is SendEmail activity. When we add it onworkflowactivated, set the to, body, subject. It doesnt send email. Can u pls give a sample code on how to send email. But it works with SPUtility.SendEmail. Then what is the point in having SendEmail activity in tools?

    TIA

    ReplyDelete
  24. Hi i'm also facing this same issue that when i add a listView WebPart on a custom page , the Approve/Reject Selection' does not appear there..can you please tell how to make it visible on custom page?

    ReplyDelete
  25. Hi Sohel,

    its a great post. whatever i am looking for exacly same one. But problem is once i am deploying the solution, "Approve/Reject Selection" button is showing on Ribbon. but button is not visible, not even any button is visible. if i click on button, nothing is happening (All Ribbon buttons are grey).Am i missing anything?? Please let me know as your earliest convenience. i am using IE9.

    Thanks

    ReplyDelete
  26. Great solution. One question is there any way to get this to work for a document library as well?

    ReplyDelete
  27. @ Anonymous. yes its working fine with document library too. just you need to change Location like below code.Then you good to go.

    ReplyDelete
    Replies
    1. Please let me know where you changed the location??

      Delete
    2. Please could anyone help me with how to change this for document library? I can see a location at elements.xml. What should I change it to?

      Delete
  28. @Sohel:- I want to add some image in the Ribbon as well. How can I do that?

    ReplyDelete
  29. @Prateek, you can add the image as part of the button property in xml. Please check the figure 5.

    ReplyDelete
  30. Thanks for sharing this. I'm using it on my 2010 Farm. Just an FYI its out of the Box in 2013.

    ReplyDelete
  31. Sohel, I keep getting error "Object reference not set to an instance of an object" at the below lines. Do I need to create Comment and Status Columns in my custom list..? please help.

    spListItem.ModerationInformation.Comment = txtComments.Text;
    spListItem.ModerationInformation.Status = moderationStatusType;
    spListItem.Update();

    ReplyDelete
  32. Hi,I have implemented the same solution, when I am click on approved/reject solution,getting message "failed complete operation".

    Let me know required any other list for data update.

    ReplyDelete