Introduction
SharePoint has been around for a number of years and has been gaining in popularity, especially with the release of SharePoint 2010. One common task when customizing a SharePoint application is to add menu items to the Site Actions menu to provide authorized users easy access to features and functionality. In this article, I will show three ways to add your own items, with submenus, or flyouts, to the Site Actions menu.
Site Action Menu
Just about anyone who has worked with SharePoint is familiar with the Site Actions menu. It provides access to functionality, such as, enabling page editing, or with SharePoint 2010, displaying the new RibbonBar
. SharePoint 2010 has also given developers more control over this menu and the items in it. The default.master
and v4.master
masterpages include the below markup to render the basic Site Actions menu on each page where appropriate.
<sharepoint:siteactions id="SiteActions1" runat="server"
accesskey="<%$Resources:wss,tb_SiteActions_AK%>"
menunotvisiblehtml=" " prefixhtml="" suffixhtml="">
<customtemplate>
<SharePoint:FeatureMenuTemplate runat="server" featurescope="Site"
groupid="SiteActions" location="Microsoft.SharePoint.StandardMenu"
useshortid="true">
<SharePoint:MenuItemTemplate id="MenuItem_EditPage" runat="server"
clientonclicknavigateurl="javascript:ChangeLayoutMode(false);"
description="<%$Resources:wss,siteactions_editpagedescriptionv4%>"
imageurl="/_layouts/images/ActionsEditPage.png" menugroupid="100"
sequence="110" text="<%$Resources:wss,siteactions_editpage%>" />
... more items ...
</SharePoint:FeatureMenuTemplate>
</CustomTemplate>
</Sharepoint:iteactions>
One way to add additional items to the Site Action menu is, of course, to modify the master page and include another MenutItemTemplate
element.
All of the properties for MenuItemTemplate
can be found at http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webcontrols.menuitemtemplate_properties.aspx [^] and most are self-explanatory and common to any WebControl
. A few properties however need some explanation.
PermissionString
: This property supplies a comma separated list of items from the SPBasePermission
enumeration to specify what level of user permissions are required to access the menu item.
PermissionMode
: This determines how to apply the permissions specified in PermissionString
, either "Any" or "All".
ClientOnClickScript
: This controls the client-side action that is taken when the use clicks the menu item. As can be seen in the code setting, this property will delete any settings for the ClientOnClickNavigateUrl
and ClientOnClickScriptContainingPrefixedUrl
properties.
[DefaultValue(""), Category("Behavior")]
public string ClientOnClickScript
{
get
{
return this.m_clientOnClickScript;
}
set
{
if (value == null)
{
value = string.Empty;
}
this.m_clientOnClickScript = value;
this.ViewState["ClientOnClickScript"] = value;
this.m_clientOnClickNavigateUrl = null;
this.ViewState["ClientOnClickNavigateUrl"] = null;
this.m_clientOnClickScriptContainingPrefixedUrl = null;
this.ViewState["ClientOnClickScriptContainingPrefixedUrl"] = null;
this.clientOnClickUsingPostBackEvent = null;
}
}
ClientOnClickNavigateUrl
: With this property, you can set either a URL to navigate to or a JavaScript function to execute when the menu item is clicked. As you can see, it sets the ClientOnClickScript
property so it will take precedence over anything set in that property so the order in which the attributes are applied to the element will affect what happens when a user clicks on the menu item.
[Category("Behavior"), DefaultValue("")]
public string ClientOnClickNavigateUrl
{
get
{
return this.m_clientOnClickNavigateUrl;
}
set
{
if (value == null)
{
value = string.Empty;
}
value = SPUtility.GetServerRelativeUrlFromPrefixedUrl(value);
value = value.Replace("'", @"\'");
if ((value.StartsWith("javascript:") || value.StartsWith("%"))
|| value.StartsWith("?"))
{
this.ClientOnClickScript = "window.location = '" + value + "';";
}
else
{
this.ClientOnClickScript = "STSNavigate2(event,'" + value + "');";
}
this.m_clientOnClickNavigateUrl = value;
this.ViewState["ClientOnClickNavigateUrl"] = value;
}
}
ClientOnClickScriptContainingPrefixedUrl
: Similar to ClientOnClickNavigateUrl
this property allows you to set either a URL or a JavaScript function to execute. The property setter calls SPUtility.ReplaceEmbeddedPrefixedUrlsWithServerRelativeUrls
to replace any builtin prefixes, such as, ~site
or ~sitecollection
, found in the string
with the server relative URLs. Once again ClientOnClickScript
is set and the order the attribute is applied in the element is important.
[DefaultValue(""), Category("Behavior")]
public string ClientOnClickScriptContainingPrefixedUrl
{
get
{
return this.m_clientOnClickScriptContainingPrefixedUrl;
}
set
{
if (value == null)
{
value = string.Empty;
}
value = SPUtility.ReplaceEmbeddedPrefixedUrlsWithServerRelativeUrls(value, true);
this.ClientOnClickScript = value;
this.m_clientOnClickScriptContainingPrefixedUrl = value;
this.ViewState["ClientOnClickScriptContainingPrefixedUrl"] = value;
}
}
VisibilityFeatureId
: Use this property to specify a feature id (guid
) the menu item is dependant on. If the feature has not been activated for the web, the menu item will not be rendered.
Adding a SubMenu
To add a submenu, you must use a SubMenuTemplate
control rather than a MenuItemTemplate
. It should be noted here the SubMenuTemplate
can't be used to provide any actions, the ClientOnClickXXX
properties of the MenuItemTemplate
are not available, it is simply a container for other menu items.
<submenutemplate
id="MenuItem_Custom"
description="Custom menu with submenu"
runat="server"
text="Custom Menu"
sequence="100"
menugroupid="90"
imageurl="/_layouts/images/settingsIcon.png" />
<menuitemtemplate
id="MenuItem_CustomSub"
description="A submenu item"
runat="server"
text="SubMenu"
sequence="110"
menugroupid="100"
imageurl="/_layouts/images/settingsIcon.png"
clientonclickscript="alert('Submenu item clicked')" />
</submenutemplate />
The SubMenuTemplate
could, of course, contain other SubMenuTemplate
s to produce a multilevel menu structure.
Although this method may be appropriate for one-off circumstances, it is not a reproducible or a recommended practice. For a reusable solution, you should create and deploy a SharePoint feature.
Create Menus with Code
With the integration of Visual Studio 2010 and SharePoint 2010, creating features are now much easier than with previous versions of either tool. To begin creating a feature to modify the Site Action menu, create a SharePoint 2010 Module project in Visual Studio 2010, making sure to chose a Farm solution rather than Sandbox. After the project has been created, you will see the below structure. The Sample.txt file can be deleted since it isn't used.
The Elements.xml file should now look like this:
="1.0"="utf-8"
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="Module1">
</Module>
</Elements>
Remove the Module
element and replace it with a CustomAction
element as below:
<elements xmlns="http://schemas.microsoft.com/sharepoint/">
<customaction
id="MANSoftDev_SiteAction"
sequence="1"
controlclass="MANSoftDev.SiteAction.SiteActionCode"
controlassembly="MANSoftDev.SiteAction, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=2e15c7b1150d656d"
rights="ManageWeb"
location="Microsoft.SharePoint.StandardMenu"
groupid="SiteActions">
</customaction>
</elements>
The GroupId
and Location
attributes tell SharePoint where the CustomAction
will be placed. In this case, the location is StandardMenu
group is SiteActions
to place it in the Site Actions menu. A full list of Locations
and Groups
can be found at http://msdn.microsoft.com/en-us/library/bb802730.aspx[^]. The Rights attribute is a comma separated list of SPBasePermissions
enumeration values. However, unlike the previous example, the user must have all the specified rights, not any. The ControlAssembly
and ControlClass
attributes are new to SharePoint 2010 and allow you to specify the class that will control this CustomAction
. This is essential to the implementation. ControlAssembly
is the four-part name of the assembly in which the class is implemented. ControlClass
is the fully qualified name of the class. Note it must include the full namespace, just giving the class name is not enough. To produce this class, you add Class Library project to the solution and sign the assembly since it will be deployed to the GAC. After creating the project, add references to System.Web
and Microsoft.SharePoint
then, either rename the existing Class1
file, or deleted it and add another class that derives from WebControl
to end up with this.
namespace MANSoftDev.SiteAction
{
public class SiteActionCode : WebControl
{
}
}
Before proceeding, it's a good idea to add the Class Library assembly to the deployment package and have it deployed to the GAC with appropriate settings added the SafeControls
section in the web.config file. To do this, you need to modify the Package in the Visual Studio 2010 project. Open the Package designer and click the Advanced tab at the bottom.
From here, click the Add button and select "Add Assembly from Project Out..." to display the dialog. Select the Class Library project from the dropdown, notice the Location
textbox is filled with the assembly name. Next, click the "Click here to add a new item." button in the Safe Controls grid and fill in the appropriate settings. Click the OK button to return to the previous screen and notice the information has been added to the Additional Assemblies grid. Once again, ensure the SharePoint project is not a Sandboxed solution or the assmbly will not be deployed to the GAC regardless of these settings.
Now that the assembly has been added to the deployment package, we can get on with the coding. To add the necessary MenuItemTemplate
and SubMenuTemplates
to recreate the previous example, you will need to override the CreateChildControls
method and add the code shown below:
protected override void CreateChildControls()
{
SubMenuTemplate sub = new SubMenuTemplate();
sub.Text = "Custom Menu";
sub.Description = "Custom menu with submenu";
sub.ImageUrl = "/_layouts/images/settingsIcon.png";
sub.MenuGroupId = 90;
sub.Sequence = 100;
Controls.Add(sub);
MenuItemTemplate item = new MenuItemTemplate("SubMenu");
item.Description = "A submenu item";
item.ImageUrl = "/_layouts/images/settingsIcon.png";
sub.Controls.Add(item);
SubMenuTemplate sub2 = new SubMenuTemplate();
sub2.Text = "Custom Menu";
sub2.Description = "Custom menu with submenu";
sub2.ImageUrl = "/_layouts/images/settingsIcon.png";
sub.Controls.Add(sub2);
item = new MenuItemTemplate("SubMenu");
item.Description = "A submenu item";
item.ImageUrl = "/_layouts/images/settingsIcon.png";
sub2.Controls.Add(item);
}
There isn't anything surprising or difficult here, it's just a matter of creating the controls and adding them to proper Controls
collection in the correct order. The properties that were mentioned in the first section can still be applied here, for simplicity though no OnClientClickXXX
properties have been set.
Create Menu from Elements.xml
Although creating the menu from code is relatively simple and easy, some may prefer to use a more declarative approach and define the menu structure within an Elements.xml file. However, this still requires code to add the menu items to the Site Actions menu. Start by adding another class to the Class Library project that is once again derived from WebControl
and implements the CreateChildControls
method. Then add another module to the SharePoint project. Remove the Sample.txt file as before and copy the contents of the previous Elements.xml file into the one just created and change the ControlClass
attribute to match the newly created class.
<elements xmlns="http://schemas.microsoft.com/sharepoint/">
<customaction
id="MANSoftDev_SiteAction"
sequence="1"
controlclass="MANSoftDev.SiteAction.SiteActionDeclarative"
controlassembly="MANSoftDev.SiteAction, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=2e15c7b1150d656d"
location="Microsoft.SharePoint.StandardMenu"
groupid="SiteActions">
</customaction>
</elements>
Now that you have a starting point, you can start to add the menu items as CustomAction elements in the Elements.xml file. The important attributes are the Location
and GroupId
. These will be used in the code to create the FeatureMenuTemplate
corresponding to the menu group.
<elements xmlns="http://schemas.microsoft.com/sharepoint/">
<customaction
id="MANSoftDev_SiteAction"
sequence="1"
controlclass="MANSoftDev.SiteAction.SiteActionDeclarative"
controlassembly="MANSoftDev.SiteAction, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=2e15c7b1150d656d"
location="Microsoft.SharePoint.StandardMenu"
groupid="SiteActions">
</customaction>
<customaction
id="subMenu"
title="SubMenu"
description="A submenu item" sequence="100"
imageurl="/_layouts/images/settingsIcon.png"
location="MANSoftDev.CustomMenu"
groupid="MANSoftDev_Sub1">
</customaction>
<customaction
id="subMenu"
title="SubMenu"
description="A submenu item"
sequence="100"
imageurl="/_layouts/images/settingsIcon.png"
location="MANSoftDev.CustomMenu"
groupid="MANSoftDev_Sub2">
</customaction>
</elements>
In the ControlClass
you once again override the CreateChildControls
method to create SubMenuTemplates
and FeatureMenuTemplates
that will correspond to the CustomAction
elements shown above.
protected override void CreateChildControls()
{
SubMenus = new List<submenutemplate />();
FeatureMenus = new Dictionary<string, />();
SubMenuTemplate submenu = CreateSubMenu();
Controls.Add(submenu);
submenu = CreateSubMenu();
Controls.Add(submenu);
FeatureMenuTemplate featureMenu = CreateFeatureMenu("MANSoftDev_Sub1");
Controls.Add(featureMenu);
featureMenu = featureMenu = CreateFeatureMenu("MANSoftDev_Sub2");
Controls.Add(featureMenu);
}
private FeatureMenuTemplate CreateFeatureMenu(string groupId)
{
FeatureMenuTemplate featureMenu = new FeatureMenuTemplate();
featureMenu.Location = "MANSoftDev.CustomMenu";
featureMenu.GroupId = groupId;
FeatureMenus.Add(groupId, featureMenu);
return featureMenu;
}
The CreateSubMenu
method is straightforward and similar to what was done in the code based method used earlier. The CreateFeatureMenu
method however is where the CustomAction
elements are linked to the FeatureMenuTemplate
via the GroupId
property. If you debugged the code at this point, you would see that the FeatureMenuTemplates
contain no children; however, in the OnPreRender
method, they do. The magic happens in the CreateChildControls
method of the FeatureMenuTemplate
control. Here, SharePoint will use the internal
class SPElementProvider
to extract the CustomAction
elements and match them to the GroupId
and create MenuItemTemplates
that are added to the Controls
collection.
...
SPElementProvider availableProvider = SPElementProvider.GetAvailableProvider();
...
if (element2.Location != "CommandUI.Ribbon.Custom")
{
MenuItemTemplate menuItem = new MenuItemTemplate();
menuItem.Description = element2.Description;
menuItem.ClientOnClickNavigateUrl =
SPCustomActionElement.ServerRelativeUrlFromTokenizedUrl
(element2.UrlAction, web, list, null);
menuItem.Text = element2.Title;
menuItem.Sequence = element2.Sequence;
menuItem.MenuGroupId = element2.MenuGroupId;
menuItem.ImageUrl = element2.ServerRelativeImageUrl;
this.OnAddMenuItem(new AddMenuItemEventArgs(menuItem));
if (menuItem.Visible)
{
this.Controls.Add(menuItem);
}
...
During the OnPreRender
event, you then iterate through the FeatureMenuTemplates
and move the MenuItemTemplates
to the appropriate SubMenuTemplate
and add the second level SubMenuTemplate
to the parent level.
protected override void OnPreRender(EventArgs e)
{
while(FeatureMenus["MANSoftDev_Sub1"].Controls.Count != 0)
{
MenuItemTemplate menu = FeatureMenus
["MANSoftDev_Sub1"].Controls[0] as MenuItemTemplate;
SubMenus[0].Controls.Add(menu);
}
while(FeatureMenus["MANSoftDev_Sub2"].Controls.Count != 0)
{
MenuItemTemplate menu = FeatureMenus
["MANSoftDev_Sub2"].Controls[0] as MenuItemTemplate;
SubMenus[1].Controls.Add(menu);
}
SubMenus[0].Controls.Add(SubMenus[1]);
base.OnPreRender(e);
}
As you can see, this method is a little more complicated than the previous methods and requires coordination between the Elements.xml and the code. However, it could allow one team member to define the menus while another codes the behavior or to create a method that could be reused in many projects.
Conclusion
I have presented three different methods to construct and deploy SiteAction menu items, with sub menus, for SharePoint 2010. Neither method can be said to be "better" than another and which one to use will be determined by your requirements. This article was meant to present the different techniques and give you the knowledge to choose the best approach for your project.
History
- Initial posting: 11/14/2010