Thursday, January 30, 2014

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”.

Reference: 

using some of the SPUtility Methods

HandleAccessDenied using

SPUtility.HandleAccessDenied(new AccessDeniedException("ACCESS DENIED"));
(or)
SPUtility.HandleAccessDenied(new Exception("You do not have access to this page."));


SendEmail using

SPUtility.SendEmail(webObj, false, false, "e-mail_address", "Web Discussion Report", Msg);
(or)
StringDictionary headers = new StringDictionary();
headers.add("to","reginald@myfatblog.co.uk");
headers.add("cc","thisiscopy@myfatblog.co.uk");
headers.add("bcc","thisistheblindcopy@myfatblog.co.uk");
headers.add("from","MySharePointEmail@myfatblog.co.uk");
headers.add("subject","How to use SendEMail from SPUtility");
headers.add("content-type","text/html"); //This is the default type, so isn't neccessary.
string bodyText ="This is the body of my email, in html format.";
SPUtility.SendEmail(web, headers, bodyText);
using GetFullUrl
Some SharePoint objects expose relative paths.
Relative paths can be converted to full Url by adding current server, site paths.
To convert a relative path to the full URL, you add the current server,
Rather than performing path manipulation, "GetFullUrl" method can be used.
Example- Get Full Url Of A SharePoint List
SPUtility.GetFullUrl(SPContext.Current.Site, listobj.DefaultDisplayFormUrl);
Example- Get Full Url Of A SharePoint ListItem
SPUtility.GetFullUrl(SPContext.Current.Site,"/"+ itemobj.Url);
Using GetListGuid
Guid id = SPUtility.GetListGuid(webObj,strListName);
Using Redirect
SPUtility.Redirect(“/MyFolder/MyPage.aspx”, SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current);
On SharePoint 2010 it will redirect tohttp://yoursite/_layouts/MyFolder/MyPage.aspx while on SharePoint 2013 it will redirect to http://yoursite/_layouts/15/MyFolder/MyPage.aspx. You see that in SharePoint 2013 the URL it contains the HIVE folder while in 2010 it doesn’t. Using the Redirect method with the RelativeToLayoutsPage option you don’t have to worry about that.
Other Reference:

TransferToErrorPage and TransferToSuccessPage

SPUtility.TransferToErrorPage("Hi this is an Error Page {0}, {1}", "click here to Go to Top site", "{your_url}");
SPUtility.TransferToSuccessPage("Hi this is an Sucess Page");
Reference:

Sunday, January 26, 2014

Access SPList object using web.Lists or web.GetList

Accessing SPList Object using web.Lists method

                   SPList listObj = webObj.Lists["DataTypes"];
                    int itemcount = listObj.ItemCount;

Accessing SPList Object using web.GetList method            
                   
                    SPList listObj2 = webObj.GetList(webObj.ServerRelativeUrl.TrimEnd('/') + "/Lists/DataTypes");
                    int itemcount2 = listObj2.ItemCount;

Some times we may change the list name after creating the list. In that case web.Lists accessing method gives error. So better to adopt GetList method when accessing lists.
                   


which user using when using RunWithElevatedPrivileges in coding

Some times we are blindly using RunWithElevatedPrivileges in the code. But we should know what user context using when using that. Some times we are blindly thinking that if we wrap the code in elevated privileges, its running in app pool account. But its wrong.

Observe the below two code blocks.
In first code block user is App pool accont user, but in 2nd senario user is current logedin user. Even u wrap the code its using current login user because your using spcontext.

1. SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite("http://xrm:1234"))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        SPUser user = web.CurrentUser;
                        string username = user.LoginName;
                   }
                }
             });


2. SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                    SPSite site = new SPSite(SPContext.Current.Site.ID);
                    SPWeb web = SPContext.Current.Web;
                    SPUser user2 = SPContext.Current.Web.CurrentUser;
                    string currentUser2 = user2.LoginName;
            });



Saturday, January 25, 2014

Find the Number of Users Currently Connected to SharePoint Site

Had to perform an unplanned IIS Reset to fix an issue in production SharePoint Farm. But executing IISReset breaks ongoing user sessions and give "Service Unavailable" error message, isn't it? Wouldn't it be a good idea to find the No. of users currently connected with the SharePoint site and do the IISReset for a minimal impact? Sure!

How to find how many users connected with SharePoint sites? use Performance Monitor!
  • Go to Start >> Run >> Type "Perfmon", to fire up Performance Monitor.
  • Click on "Performance Monitor" , Right click "Add Counters" in Graph windowsharepoint how many users connected
  • Under the available Counters, Pick "Web Service" expand the node and select "Current Connections"
  • Under "Instances of Selected object" You can either select "Total" or pick a particular web applicationFind the number of users Currently Connected to SharePoint Site
  • Click on Add button to add the selected counter, and click "OK" button
Now the graph will show the how many users connected to your SharePoint Site(s).
sharepoint number of users connected
Please not, this gives only the no of user sessions currently ongoing. But not the one already completed (Doesn't count the users already opened the site and left it idle!) There are possibilities that users may hit the SharePoint URL meanwhile and receive "Service Unavailable" message since IIS is reset!

Reference:

http://www.sharepointdiary.com/2013/05/find-number-of-users-currently-connected-to-sharepoint.html

Show Quick Launch in SharePoint 2010 Web Part Pages

By default, SharePoint 2010 Web part pages don't have quick launch navigation! Web part page missing quick launch in SharePoint 2010.  
show quick launch sharepoint web part page
show quick launch in SharePoint web part page
Show quick launch SharePoint 2010 web part page:
We can bring it by editing web part page. Just Edit the webpart page in the SharePoint Designer 2010 Advanced mode and remove the following code (As same in SharePoint 2007):
add quick launch to sharepoint web part page
?
1
2
<asp:Content ContentPlaceHolderId="PlaceHolderNavSpacer" runat="server"></asp:Content>
<asp:Content ContentPlaceHolderId="PlaceHolderLeftNavBar" runat="server"></asp:Content>
sharepoint 2010 quick launch missing from web part page

In SharePoint 2010 you have to also remove the following CSS code: 
?
1
2
3
4
5
6
7
8
9
10
11
12
<style type="text/css">
 
body #s4-leftpanel
{
display:none;
}
.s4-ca
 
{
margin-left:0px;
}
</style>
sharepoint display quick launch web part page
Save the page! This will bring Quick launch bar again!! Here is the result: quick launch on SharePoint 2010 web part page. 
sharepoint 2010 add quick launch menu to web part page
This solves the issue of SharePoint 2010 quick launch missing from web part page. Here is my SharePoint 2007 version of this post: SharePoint 2007 Quick Launch (left navigation) Missing in Web part pages - Solutions


Reference:

http://www.sharepointdiary.com/2013/03/show-quick-launch-in-sharepoint-2010-web-part-pages.html

Image noise comparison methods

 1. using reference image technique     - peak_signal_noise_ratio (PSNR)     - SSI 2. non-reference image technique     - BRISQUE python pac...