Deploying Custom Action to Specific Lists in SharePoint 2010

There are times when our customers want a custom action menu for a specific list in SharePoint. The custom action menu must be available when we navigate to a specific list and not be available for other lists.

This article details a method to create a custom action in SharePoint that can be deployed to a specific list.

Here, we do not build the custom action using modules or the SharePoint designer. We are building it via a feature. The custom action is defined and bound to the ribbon on feature activation and removed on feature deactivation.

Let's first understand the requirements here:

  1. When the SharePoint user logs in to a SP site and navigates to a specific list, a custom action (for example, "Seal Item") must be available for the items of the list.
  2. The custom action must not be available for other lists in the site.
  3. When the user clicks on the custom action, a column in the list (for example, Status) must changed to "Sealed".

I have a SharePoint list named "Products" with the following columns:

SharePoint list –named-Products

Here, we will create the custom action using a feature.

1. In Visual Studio create a new project and add the following code to the Feature Activated event.

public override void FeatureActivated(SPFeatureReceiverProperties properties)

        {

            SPSite site = properties.Feature.Parent as SPSite;

            using (SPWeb web = site.OpenWeb())

            {

                SPList list = web.Lists.TryGetList("Products");

                if (list != null)

                {

                    var actionUnlockPayment = list.UserCustomActions.Add();

                    actionUnlockPayment.Location = "CommandUI.Ribbon.ListView";

                    actionUnlockPayment.Rights = SPBasePermissions.ManageLists

                        | SPBasePermissions.CancelCheckout | SPBasePermissions.AddListItems

                        | SPBasePermissions.EditListItems | SPBasePermissions.DeleteListItems

                        | SPBasePermissions.ViewListItems | SPBasePermissions.ApproveItems

                        | SPBasePermissions.OpenItems | SPBasePermissions.ViewVersions

                        | SPBasePermissions.DeleteVersions | SPBasePermissions.CreateAlerts

                        | SPBasePermissions.ViewFormPages | SPBasePermissions.ManagePermissions

                        | SPBasePermissions.ViewUsageData | SPBasePermissions.ManageWeb

                        | SPBasePermissions.AddAndCustomizePages | SPBasePermissions.ApplyThemeAndBorder

                        | SPBasePermissions.ApplyStyleSheets | SPBasePermissions.BrowseDirectories

                        | SPBasePermissions.CreateSSCSite | SPBasePermissions.ViewPages

                        | SPBasePermissions.EnumeratePermissions | SPBasePermissions.BrowseUserInfo

                        | SPBasePermissions.ManageAlerts | SPBasePermissions.UseRemoteAPIs

                        | SPBasePermissions.UseClientIntegration | SPBasePermissions.Open

                        | SPBasePermissions.EditMyUserInfo | SPBasePermissions.CreateGroups

                        | SPBasePermissions.ManagePersonalViews | SPBasePermissions.AddDelPrivateWebParts

                        | SPBasePermissions.UpdatePersonalWebParts;

                    actionUnlockPayment.Sequence = 100;

                    actionUnlockPayment.Title = "Seal Record";

                    actionUnlockPayment.CommandUIExtension = @"<CommandUIExtension><CommandUIDefinitions>

                    <CommandUIDefinition Location=""Ribbon.ListItem.Actions.Controls._children"">

                    <Button Id=""{245CAE99-A250-4E2F-8448-A108ABE688A3}"" Sequence=""100"" TemplateAlias=""o1""

                     Image16by16=""_layouts/Seal/Images/Seal-icon-16.png""

                                      Image32by32=""_layouts/Seal/Images/Seal-icon-32.png""

                    Command=""{A139C8C3-3475-4DAE-92AB-57C49C542D6C}"" CommandType=""General"" LabelText=""Seal Record"" />

                    </CommandUIDefinition>

                    </CommandUIDefinitions>

                    <CommandUIHandlers>

                    <CommandUIHandler Command =""{A139C8C3-3475-4DAE-92AB-57C49C542D6C}"" CommandAction=""javascript:SealRecord();"" />

                    </CommandUIHandlers></CommandUIExtension>";

                    actionUnlockPayment.Update();

 

                    var scriptlink = site.UserCustomActions.Add();

                    scriptlink.Location = "ScriptLink";

                    scriptlink.ScriptSrc = "/_layouts/Seal/JScript1.js";

                    scriptlink.Update();

                }

            }

        }

We are first getting the handle to the list that we want to show the custom action for, and adding a custom action item.

We specify the location of the custom action "CommandUI.Ribbon.ListView"; that is, we want the custom action for a list item (similar to new / edit item).

We specify the rights needed for a user to see this custom action. In this case, the permissions are those available for default contributor settings.

Next we specify the sequence (where in the group, the action must appear). This is especially important if you have more than one custom action. I have noted that this must of the form 100, 200 .... Any other sequence - such as 100, 101.. or 20, 21 and so on seems to fail in a multi-action scenario. The behaviour becomes random after a postback if the sequence is not in multiples of 100.

Next, we specify the title for the action; in our case it is Seal Record.

Next, we specify the CommandUIExtenstion. Here we give a GUID to the button (can be obtained from GUID creator utility in Visual Studio) and supply the 16*16 and 32*32 icons for the button. We also specify a GUID for the command handler, that has a corresponding action that calls a JavaScript method to fire when the user clicks on the custom action button. In case of SharePoint 2013, the relative URL to the layouts folder would need to be appended with a "/15/". You can refer to this article for further details.

We conclude by providing the location of the JavaScript file, where the handler method is defined.

2. Next, add the following code to the feature deactivating event:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

        {

            SPSite site = properties.Feature.Parent as SPSite;

            using (SPWeb web = site.OpenWeb())

            {

                SPList list = web.Lists.TryGetList("Products");

                if (list != null)

                {

                    list.UserCustomActions.Clear();

                }

            }  

        }

This is to ensure that when the feature is deactivated, the custom actions in the ribbon are removed too.

3. Next, we must add the images and the JavaScript files we specified in the feature activated handler.

In the Visual Studio project, add a "Mapped folder" to Layouts as shown below and create a subfolder, named Images and add the 2 icon images (relevant to your custom action) to this folder.

Mapped folder

Next add a new JavaScript item to the project folder and name it Jscript1.js. Remember, we provided the same name in Step #1, where we specified the handler file and method name.

4. Now, we need to implement the JavaScript handler. In the js file created in the previous step, add the 2 methods as below.

function SealRecord() {

    var ctx = SP.ClientContext.get_current();

    var ItemIds = "";

    //get current list id

    var listId = SP.ListOperation.Selection.getSelectedList();

    // get list name

    var web = ctx.get_web();

    var list = web.get_lists().getById(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 + ",";

    }

 

    var pageUrl = SP.Utilities.Utility.getLayoutsPageUrl('/Seal/SealRecord.aspx?ids=' + ItemIds + '&listid=' + listId);

    var options = SP.UI.$create_DialogOptions();

    options.width = 250;

    options.height = 100;

    options.url = pageUrl;

    options.dialogReturnValueCallback = Function.createDelegate(null, OnDialogClose);

    SP.UI.ModalDialog.showModalDialog(options);   

}

//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);

    }

}

Here, we are iterating through the selected items and capturing the ids in an array and calling an application page "SealRecords.aspx" with the ids as a QueryString paramater. We are also specifying that after the call is done, when control returns back, invoke the "OnDialogClose" method, where we are reloading the base page.

5. Next, let us create the application page "SealRecords.aspx", where the actual update of the records occurs.

Add a new application page to the mapped layouts folder and add a button to the page. Here is the code of the content in the aspx file.

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">

</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">

<script type="text/javascript">

    function closeDialog() {

        SP.UI.ModalDialog.commonModalDialogClose(0, 'Cancelled clicked');

    }

    function finisheDialog() {

        SP.UI.ModalDialog.commonModalDialogClose(1, 'Finished operation');

    }

    </script>

    <div style="text-align: center">

        <asp:Button ID="btnConfirm" runat="server" Text="Seal Records" Visible="false" OnClick="btnConfirm_Click" />

        <br /><br />

        <asp:Label ID="lblMessage" runat="server"></asp:Label>

    </div>

</asp:Content>

<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">

Seal Record

</asp:Content>

<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >

Seal Record

</asp:Content>

Use the following code for the code behind of this application page.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Reflection;

using System.Text;

using System.Web;

using System.Web.UI;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Client;

using Microsoft.SharePoint.WebControls;

 

namespace Seal.Layouts.Seal

{

    public partial class SealRecord : LayoutsPageBase

    {

       

        private List<int> ItemIDs

        {

            get

            {

                string ids = HttpUtility.UrlDecode(Request.QueryString["ids"]).Trim(new[] { ',' });

                return ids.Split(new[] { ',' })

                    .Select(strId => Convert.ToInt32(strId))

                    .ToList();

            }

        }

 

        List<string> values = new List<string>();

        protected void Page_Load(object sender, EventArgs e)

        {          

          btnConfirm.Visible = true;          

        }

 

        protected void btnConfirm_Click(object sender, EventArgs e)

        {

            try

            {

                using (SPSite oSite = new SPSite(SPContext.Current.Site.ID))

                {

                    using (SPWeb oWeb = oSite.OpenWeb())

                    {

                        oWeb.AllowUnsafeUpdates = true;

                        SPList oList = oWeb.Lists.TryGetList("Products");

                        bool pageRefresh = false;

                        if (ItemIDs.Count > 0)

                        {

                                StringBuilder strMessage = new StringBuilder();

                                foreach (int item in ItemIDs)

                                {

                                    SPListItem ListItem = oList.GetItemById(item);

                                    if (ListItem != null)

                                    {

                                        if (ListItem["Status"] != null)

                                        {

                                            string docStatus = ListItem["Status"].ToString();

                                            //Check if selected item is locked

                                            if (docStatus.Equals("Open")

                                            {

                                                    ListItem["Status"] = "Sealed";                                                       

                                                    ListItem["Editor"] = SPContext.Current.Web.CurrentUser;

                                                    ListItem.Update();

                                                    pageRefresh = true;                                                    

                                            }

                                        }

                                    }

                                }

                                string script = string.Empty;                                  

 

                                if (pageRefresh)

                                {

                                    //If any one record is processed, refresh the page

                                    ScriptManager.RegisterStartupScript(this, GetType(), "Message", "finisheDialog();", true);

                                }

                               

                        }

                        oWeb.AllowUnsafeUpdates = false;

                    }

                }              

            }

            catch (Exception ex)

            {

               

            }

        }

 

    }

}


Here, on click of the button "btnConfirm", we are iterating the list for each of the selected records and updating the status to "Sealed". Once all the records are updated, we are passing back to JavaScript to refresh the page.

Here are some screenshots of the preceding solution. The main focus here is to register the custom action through the feature. I found this to be one effective way to associate a custom action to one specific list and not to all lists / libraries in general. Hope someone finds this useful.

SharePoint list1

SharePoint list2

SharePoint list3