Tuesday, January 31, 2012

How to get the logged on users name from a SharePoint page using javascript


OPTION 1 Using Javascript to find the ‘zz8_Menu’ element in HTML

Choosing this method will only get the users name and won’t make a call to external services to find out who is viewing the page. If you only need the name of the user, i’d recommend this option since there isn’t a need to make an unncessary call to the server.

view sourceprint?

*** JAVASCRIPT METHOD ***

< script language="javascript">
var whoami = document.getElementById("zz8_Menu").innerHTML;
var end = whoami.indexOf("< ");
var nameOnly = whoami.substring(8, end);
alert(whoami);
< /script>


OPTION 2 Using the SPServices jQuery Library

This option will query the SharePoint web services to get user information, including the user id and other properties.



view sourceprint?
*** jQUERY METHOD ***
var userName = $().SPServices.SPGetCurrentUser({
fieldName: "Title",
debug: false
});
This option is best and will provide less room for error and more information.

Sunday, January 29, 2012

Get current loggedin user in Sharepoint using Javascript

This a script that gets the current logged-in user’s name from the welcome menu and populates it in a People Picker control.
Firstly, to run the function on page sharepoint load user spBodyOnLoadFunctionNames something like below.
_spBodyOnLoadFunctionNames.push(“AddCurrentUserToPP”);
Next add this script in any content editor webpart or just on your custom page.
function AddCurrentUserToPP()
{
var currentUser = GetUser();
if(currentUser != null)
{
var pp = GetPeoplePickerText(document.getElementById(‘PeoplePickerId’).parentNode);
//set the People picker field to current user
if(pp != null)
pp.innerText = currentUser;
}
}
function GetUser()
{
var tags = document.getElementsByTagName(‘a’);
for (var i=0; i < tags.length; i++)
{
if(tags[i].innerText.substr(0,7) == ‘Welcome’)
{
return tags[i].innerText.substr(8,tags[i].innerText.length);
}
}
}
function GetPeoplePickerText(container)
{
var result = “”;
var _divs = container.getElementsByTagName(“DIV”);
for(var k=0; k < divs.length; k++)
{
if(divs[k].id.indexOf(identifier) > 0 && divs[k].id.indexOf(“UserField_upLevelDiv”) > 0)
{
result = divs[k];
break;
// You can also access the text in the control by using the below
var innerSpans = divs[k].getElementsByTagName(“SPAN”);
for(var j=0; j < innerSpans.length; j++)
{ if(innerSpans[j].id == ‘content’)
{ return innerSpans[j].innerHTML;}
}
}
return result;
}

Thursday, January 26, 2012

USING THE CURRENT PAGE URL IN THE URLACTION OF A SHAREPOINT FEATURE


With the introduction of the feature framework in SharePoint 2007, developers have some great opportunities to customize and enhance nearly everything in SharePoint. One of the things that's quite easy to do with the help of a feature, is to add and/or replace functionality in the web user interface of SharePoint site. This was typically very hard to do (in a nice way) for the previous version of SharePoint (remember having to edit a javascript file to add a menu item in the ECB?).

Let's take the following example of a feature's manifest file:

< Elements xmlns="http://schemas.microsoft.com/sharepoint/">
< CustomAction
Id="{6FCB0F81-2105-4d9f-96BF-C48A19B8E439}"
Title="My Link"
Location="Microsoft.SharePoint.StandardMenu"
GroupId="SettingsMenu">
< UrlAction Url="_layouts/mypage.aspx"/>
< /CustomAction>
< /Elements>

When activated, the feature will add a menu item to the Settings menu of any SharePoint list or document library. The menu item will have the title My Link, when clicked the user will navigate to the page mypage.aspx in the _layouts folder (the typical place to deploy your custom application pages).



If you build some functionality in the mypage.aspx, in many scenarios this page will need to know from which list the link originated. This can be done by using URL tokens in the UrlAction element:

< UrlAction Url="_layouts/mypage.aspx?listid={ListId}"/>
The {ListId} URL token will be automatically replaced with the ID of the list, in which the menu item is shown. In the mypage.aspx, you can retrieve the value of the listid parameter by making use of the QueryString. Once you've got the ID, the object model can be used to get a reference to the SPList instance of that list. According to the documentation on MSDN, the following URL tokens can be used:

~site - Web site (SPWeb) relative link.
~sitecollection - site collection (SPSite) relative link.
In addition, you can use the following tokens within a URL:
{ItemId} - Integer ID that represents the item within a list.
{ItemUrl} - URL of the item being acted upon. Only work for documents in libraries. [Not functional in Beta 2]
{ListId} - GUID that represents the list.
{SiteUrl} - URL of the Web site (SPWeb).
{RecurrenceId} - Recurrence index. This token is not supported for use in the context menus of list items.
Unfortunately there is no token that will give you the URL of the page on which the feature's link is being displayed. In many cases you want to have that URL to be able to redirect, after you've shown your custom functionality, to the originating page. SharePoint itself uses this technique a lot: in many URL's you'll find the Source parameter:

http://wss.u2ucourse.com/Lists/Links/NewForm.aspx?Source=http%3A%2F%2Fwss%2Eu2ucourse%2Ecom%2Fdefault%2Easpx

The URL above points to the NewForm.aspx for the a Links list. Normally when the user fill's out this form and clicks OK, this page redirects to the default view of the list. Because this link has the Source parameter, when the user clicks OK (or cancel), the page will redirect to the default.aspx instead. You can add the Source parameter to a lot of pages in SharePoint, giving you full control over the redirecting.

So the issue is: we want to include the URL of the originating page in the UrlAction element of the feature's CustomAction, but all we get are a bunch of ID's and some URL's that are not useful for this scenario. As usual peeking in the machine room of SharePoint itself can give you some good ideas to solve this issue. The SharePoint guys themselves sometimes use Javascript functions in the UrlAction, instead of ordinary hyperlinks. Thus with some clever use of Javascript, it's quite easy to solve the problem:

< UrlAction Url="javascript:window.location= '{SiteUrl}/_layouts/mypage.aspx?List={ListId}&Source=' + window.location"/>

The actual link is a Javascript function that will navigate to a specific URL. This URL is a concatenation of the URL of the page to display (including for example the ID of the list as a parameter in the QueryString), and the Source parameter which is dynamically set the current page's URL. Et voila, the constructed link will point to your page, and the redirect will always point to the page you started from.

Extra: this tric can also be used to overcome a bug in SharePoint that causes a URL token of a CustomAction to be replaced only once. So if you have used the ListID token two times in a UrlAction element, only one of the token's will be replaced with the actual ID of the list. The user "FlatEric" (what's in a name?) explains this in the Community Content of the How to: Add Actions to the User Interface article on MSDN.

I found an ugly way to bypass this flaw:
< UrlAction Url="javascript:function process(){var site='{SiteUrl}';var item={ItemId};window.location.href=site+'/Lists/MyList/NewForm.aspx?ID='+item+'&Source='+site+'/Lists/myOtherList/DispForm.aspx?ID='+item;};process();"/>

Tuesday, January 24, 2012

Hide the Sign In link for the anonymous user - SharePoint MOSS

Following are the two steps to implement this requirement in the supported way and its quite easy, thanks to master page and the SharePoint Application Page link control.

1. Create a custom user control based on the OOB “WelCome.ascx” control. Override the “OnLoad” event and hide the “Sign In” application page link for the anonymous access user.

2. Create a custom master page based on the any OOB parent master page with respect to your requirement and site definition. Render the Custom welcome control in the place of OOB welcome control.

You can find the Welcome.ascx user control under the “Control Templates” folder. Bunch of menu items are available for the authenticated user like My Settings, Sign in as different user, Log Out and Personalize the page. All these menu items are available as feature menu template and will be available only if the user was authenticated successfully. Following is the structure of the feature menu template and all the menu items are available under the ID “ExplicitLogOut”. You can see that the visibility of this Personal Actions control is false and the visibility will be made to true when the user is successfully authenticated.

< SharePoint:PersonalActions AccessKey="< %$Resources:wss,personalactions_menu_ak%>" ToolTip="< %$Resources:wss,open_menu%>" runat="server" id="ExplicitLogout" Visible="false">

< CustomTemplate>

< SharePoint:FeatureMenuTemplate runat="server"

FeatureScope="Site"

Location="Microsoft.SharePoint.StandardMenu"

GroupId="PersonalActions"

id="ID_PersonalActionMenu"

UseShortId="true"

>

< SharePoint:MenuItemTemplate runat="server" id="ID_PersonalInformation"

Text="< %$Resources:wss,personalactions_personalinformation%>"

Description="< %$Resources:wss,personalactions_personalinformationdescription%>"

MenuGroupId="100"

Sequence="100"

ImageUrl="/_layouts/images/menuprofile.gif"

UseShortId="true"

/>

< SharePoint:MenuItemTemplate runat="server" id="ID_LoginAsDifferentUser"

Text="< %$Resources:wss,personalactions_loginasdifferentuser%>"

Description="< %$Resources:wss,personalactions_loginasdifferentuserdescription%>"

MenuGroupId="200"

Sequence="100"

UseShortId="true"

/>

< SharePoint:MenuItemTemplate runat="server" id="ID_RequestAccess"

Text="< %$Resources:wss,personalactions_requestaccess%>"

Description="< %$Resources:wss,personalactions_requestaccessdescription%>"

MenuGroupId="200"

UseShortId="true"

Sequence="200"

/>

< SharePoint:MenuItemTemplate runat="server" id="ID_Logout"

Text="< %$Resources:wss,personalactions_logout%>"

Description="< %$Resources:wss,personalactions_logoutdescription%>"

MenuGroupId="200"

Sequence="300"

UseShortId="true"

/>

< SharePoint:MenuItemTemplate runat="server" id="ID_PersonalizePage"

Text="< %$Resources:wss,personalactions_personalizepage%>"

Description="< %$Resources:wss,personalactions_personalizepagedescription%>"

ImageUrl="/_layouts/images/menupersonalize.gif"

ClientOnClickScript="javascript:MSOLayout_ChangeLayoutMode(true);"

PermissionsString="AddDelPrivateWebParts,UpdatePersonalWebParts"

PermissionMode="Any"

MenuGroupId="300"

Sequence="100"

UseShortId="true"

/>

< SharePoint:MenuItemTemplate runat="server" id="ID_SwitchView"

MenuGroupId="300"

Sequence="200"

UseShortId="true"

/>

< SharePoint:MenuItemTemplate runat="server" id="MSOMenu_RestoreDefaults"

Text="< %$Resources:wss,personalactions_restorepagedefaults%>"

Description="< %$Resources:wss,personalactions_restorepagedefaultsdescription%>"

ClientOnClickNavigateUrl="javascript:MSOWebPartPage_RestorePageDefault()"

MenuGroupId="300"

Sequence="300"

UseShortId="true"

/>

< /SharePoint:FeatureMenuTemplate>

< /CustomTemplate>

< /SharePoint:PersonalActions>

The another part of the welcome user control is “ExplicitLogin” which has been rendered as the SharePoint Application Page Link as follows.

< SharePoint:ApplicationPageLink runat="server" id="ExplicitLogin"

ApplicationPageFileName="Authenticate.aspx" AppendCurrentPageUrl=true

Text="< %$Resources:wss,login_pagetitle%>" style="display:none" Visible="false" />

This is the link which we need to concentrate for this requirement. By default this link visibility is false and will come alive when the user is not authenticated. This is what happens with the anonymous access user. When the anonymous user access the site this link is visible so that the unauthenticated user can sign in.

Fair enough on the post mortem of the welcome user control. Now copy this welcome user control and paste it under the Control templates folder as “CustomWelcome.ascx” control. In the “CustomWelcome.ascx” control add an In Line script and override the “OnLoad” event. In the “OnLoad” event for the unauthenticated user hide the “ExplicitLogin” link.

protected override void OnLoad(EventArgs e)

{

//base.OnLoad(e);

base.OnLoad(e);

if (HttpContext.Current.User.Identity.IsAuthenticated)

{

this.ExplicitLogout.Visible = true;

}

else

{

this.ExplicitLogin.Visible = false;

this.ExplicitLogin.Attributes.CssStyle.Add("display", "block");

}

}

Now we are done with the custom welcome user control. Let us have a look on rendering it through the custom master page based on the “default.master” master page. Copy the default.master page and add the Tag prefix reference for the “CustomWelcom.ascx” control as follows in the custom master page :

< %@ Register TagPrefix="wssuc" TagName="CustomWelcome" src="~/_controltemplates/CustomWelcome.ascx" %>

Find the following entry in the master page :

< wssuc:Welcome id="IdWelcome" runat="server" EnableViewState="false">

< /wssuc:Welcome>

Replace the above entry with the following entry to replace the OOB welcome user control with your custom welcome user control :

< wssuc:CustomWelcome id="IdWelcome" runat="server" EnableViewState="false">

< /wssuc:Welcome>

Save the custom master page and use it for the public facing internet site and now “Sign In” link will not be available for the unauthenticated anonymous access user.

Friday, January 20, 2012

SharePoint custom security trimming

Ok, so this turned out not to be so custom, but it was annoying enough to figure it out that I wanted to share.

My problem: I've created some custom widgets in my master page, but I only want Full Control people and Contributors to see them, not Readers, etc. So, I tried to use the SPRole to figure out what roles the current user was in. It didn't work as expected, plus it's depricated. So I moved on to the SPRoleDefinition and SPRoleAssignment classes. These also didn't give me what I wanted. Finally I did a search on tapping into SharePoint's security trimming, and found this gem.

Wrap the following tag around the thing that you want security trimmed, and SharePoint will take care of it for you.

your stuff goes here

< SharePoint:SPSecurityTrimmedControl id="something" runat="server" PermissionsString="AddAndCustomizePages"> your stuff goes here < /SharePoint:SPSecurityTrimmedControl>

The important bit is what goes inside the PermissionsString attribute.


Here's a list of possible values:

List Permissions
ManageLists
CancelCheckout
AddListItems
EditListItems
DeleteListItems
ViewListItems
ApproveItems
OpenItems
ViewVersions
DeleteVersions
CreateAlerts
ViewFormPages

Site Permissions
ManagePermissions
ViewUsageData
ManageSubwebs
ManageWeb
AddAndCustomizePages
ApplyThemeAndBorder
ApplyStyleSheets
CreateGroups
BrowseDirectories
CreateSSCSite
ViewPages
EnumeratePermissions
BrowseUserInfo
ManageAlerts
UseRemoteAPIs
UseClientIntegration
Open
EditMyUserInfo

Personal Permissions
ManagePersonalViews
AddDelPrivateWebParts
UpdatePersonalWebParts

Tuesday, January 17, 2012

How to check user coming from Mobile App or Desktop Browser

Detecting a mobile browser in ASP.NET

Code :

public static bool isMobileBrowser()
{
//GETS THE CURRENT USER CONTEXT
HttpContext context = HttpContext.Current;

//FIRST TRY BUILT IN ASP.NT CHECK
if (context.Request.Browser.IsMobileDevice)
{
return true;
}
//THEN TRY CHECKING FOR THE HTTP_X_WAP_PROFILE HEADER
if (context.Request.ServerVariables["HTTP_X_WAP_PROFILE"] != null)
{
return true;
}
//THEN TRY CHECKING THAT HTTP_ACCEPT EXISTS AND CONTAINS WAP
if (context.Request.ServerVariables["HTTP_ACCEPT"] != null &&
context.Request.ServerVariables["HTTP_ACCEPT"].ToLower().Contains("wap"))
{
return true;
}
//AND FINALLY CHECK THE HTTP_USER_AGENT
//HEADER VARIABLE FOR ANY ONE OF THE FOLLOWING
if (context.Request.ServerVariables["HTTP_USER_AGENT"] != null)
{
//Create a list of all mobile types
string[] mobiles =
new[]
{
"midp", "j2me", "avant", "docomo",
"novarra", "palmos", "palmsource",
"240x320", "opwv", "chtml",
"pda", "windows ce", "mmp/",
"blackberry", "mib/", "symbian",
"wireless", "nokia", "hand", "mobi",
"phone", "cdm", "up.b", "audio",
"SIE-", "SEC-", "samsung", "HTC",
"mot-", "mitsu", "sagem", "sony"
, "alcatel", "lg", "eric", "vx",
"NEC", "philips", "mmm", "xx",
"panasonic", "sharp", "wap", "sch",
"rover", "pocket", "benq", "java",
"pt", "pg", "vox", "amoi",
"bird", "compal", "kg", "voda",
"sany", "kdd", "dbt", "sendo",
"sgh", "gradi", "jb", "dddi",
"moto", "iphone"
};

//Loop through each item in the list created above
//and check if the header contains that text
foreach (string s in mobiles)
{
if (context.Request.ServerVariables["HTTP_USER_AGENT"].
ToLower().Contains(s.ToLower()))
{
return true;
}
}
}
return false;
}

Sunday, January 15, 2012

Programatically Add audiences and audience rules

Adding audiences and audiences rules programatically using SharePoint Object code

SPSecurity.RunWithElevatedPrivileges(delegate()

{

try

{

String siteUrl = args[0];

using (SPSite site = new SPSite(siteUrl))

{

ServerContext context = ServerContext.GetContextsite);

AudienceManager audManager = new AudienceManager(context);

AudienceCollection ac = audManager.Audiences;

Audience audNew1 = null;

Audience audNew2 = null;

string sNewAudience1 = "New Audience 1";

string sAudience1Description = "Description for New Audience 1";

string sNewAudience2 = "New Audience 2";

string sAudience2Description = "Description for New Audience 2";

try

{ //Create the audience for New Audience 1

audNew1 = ac.Create(sNewAudience1 , sAudience1Description);

ArrayList AudRules = new ArrayList();

AudienceRuleComponent rule1 = new AudienceRuleComponent

("", "=", "");

AudRules.Add(rule1);

AudienceRuleComponent rule2 = new AudienceRuleComponent("", "=", "");

AudRules.Add(rule2);

AudienceRuleComponent rule3 = new AudienceRuleComponent("", "=", "");

AudRules.Add(rule3);

AudienceRuleComponent rule4 = new AudienceRuleComponent("", "=", "");

AudRules.Add(rule4);

audNew1.AudienceRules = AudRules;

audNew1.Commit();

Console.WriteLine("New Audiences 1 added successfully");

//Create the audience for New Audience 1

audNew2 = ac.Create(sNewAudience2, sAudience2Description);

AudRules.Clear();

AudienceRuleComponent rule5 = new AudienceRuleComponent("", "=", "");

AudRules.Add(rule5);

AudienceRuleComponent rule6 = new AudienceRuleComponent("", "=", "");

AudRules.Add(rule6);

AudienceRuleComponent rule7 = new AudienceRuleComponent("", "=", "");

AudRules.Add(rule7);

audNew2.AudienceRules = AudRules;

audNew2.Commit();

Console.WriteLine("New Audiences 2 added successfully");

}

catch (AudienceDuplicateNameException e)

{

Console.WriteLine(e.ToString());

Console.Read();

}

}

}

catch (Exception exception)

{

Console.WriteLine(exception.ToString());

Console.Read();

}

});

Friday, January 13, 2012

Querying a SharePoint list with JavaScript

Querying a SharePoint list with JavaScript

I came across an interesting article to Query SharePoint list Items using JavaScript which I want to share with you. JavaScript uses the built in web services of MOSS and makes HTTP Request to the web service to retrieve the list items.

Use the below script functions:

function QueryListEx(listGuid, fields, where, orderBy, rowLimit, extractRows)
{ var a = new ActiveXObject("Microsoft.XMLHTTP");
if(a == null) return null;
a.Open("POST", GetRootUrl() + "_vti_bin/DspSts.asmx", false);
a.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
a.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/sharepoint/dsp/queryRequest");
var d = '' + "" +" " +" " +" 1.0" +" " +" " +" " +" " + "" + "http://schemas.microsoft.com/sharepoint/dsp\">" +" " +" " +" " + fields + "" +" " + where + "" +" " + orderBy + "" +" " +" " +" " + "" + "";

a.Send(d);
if (a.status != 200)
return null;
else
{
if (extractRows)
return a.responseXML.selectNodes('//Row');
else
return a.responseXML;
}
}

function GetRootUrl()
{
var pathparts = document.location.pathname.split('/');
var url = 'https://' + document.location.hostname + '/' + pathparts[1] + '/';
return url;
}


function GetList(listName)
{
var a = new ActiveXObject("Microsoft.XMLHTTP");
if(a == null) return null;
a.Open("POST", GetRootUrl() + "_vti_bin/Lists.asmx", false);
a.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
a.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/sharepoint/soap/GetList");
var d = "" + "" + "" + "http://schemas.microsoft.com/sharepoint/soap/\">" + "" + listName + "" + "" + "" + "";

a.Send(d);
if (a.status != 200)
return null;
else
return a.responseXML;
}

function GetListGuid(fullName)
{
var res = GetList(fullName);
if (res != null)
return res.selectSingleNode("//List").getAttribute("ID");
else
return null;
}

Calling QueryListEx to obtain results is a pretty easy process. A number of parameters are required:

listGuidThe internal guid of the list to be queried (see below).
fieldsAn XML string containing a list of fields to be returned by the query. The resultant fields will be represented in the output XML.
whereA query expression used to filter the rows from the list and create the result set. The following expression returns all records in the list where the field with the internal name of ID is equal to 1.
1
orderByAn XML fragement representing one or more fields to order the results by. Pass in an empty string to use the default sort order. The following example sorts by the Surname and Forename fields.
rowLimitSet to a positive integer to limit the size of the result set. It is always wise to restrict row sets to prevent potential performance problems or timeouts.
extractRowsSet this to true to automatically extract the results into an array of XML nodes. If set to false the entire XML DOM of the result will be returned.

Thursday, January 12, 2012

How to add a custom action to a SharePoint list actions menu for a specific list or content type.

If you have ever tried adding a SharePoint custom action to the actions menu and tried using "List" or "ContentType" as the "RegistrationType" and then tried to specify a specific list or content type in the "RegistrationId" you know that it doesn't work. SharePoint will silently not render your custom action. If you try and target a generic list using a "RegistrationId" of "100" you will see that SharePoint will gladly render your action on every list in the site. I have found a rather kludgy work around to the problem.

First off in your element manifest file you need to use the "ControlAssembly" and "ControlClass" attributes off of CustomAction. MSDN doesn't say a whole lot about these attributes but essentially they allow you to specify a web control class in your assembly which can render the action. (If you need to create hierarchical menus check out this article.) Here is the XML to get you started:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="MyCustomAction"
RegistrationType="List"
GroupId="ActionsMenu"
Location="Microsoft.SharePoint.StandardMenu"
Sequence="1000"
ControlAssembly="[Fully qualified assembly name]"
ControlClass="MyNamespace.MyCustomAction">
CustomAction>
Elements>

Next you will need to create a web control class which renders the menu item, as seen below:

public class MyCustomAction : WebControl {
private MenuItemTemplate _action;

protected override void CreateChildControls() {
SPWeb site = SPContext.Current.Web;

_action
= new MenuItemTemplate {
Text = "My Action",
Description = "My Action",
ImageUrl = "/_layouts/images/NEWITEM.GIF",
ClientOnClickNavigateUrl = "http://www.nearinfinity.com"
};

Controls.Add(_action);
}
}

Compile and deploy your solution. Oh and make sure to add your assembly and namespace to the safe control list or you will end up pulling your hair out because once again SharePoint will silently not render your menu item. You should now see the "My Action" on every list in the site. We have now reproduced what a standard CustomAction element in your element manifest would do normally. Now we need to find a way to determine which list our control is being added to. The best way I found for doing that is to traverse the control hierarchy up the parent chain until I found the containing ListViewWebPart. The code is quite simple and looks like this:

private ListViewWebPart GetParentListViewWebPart() {
Control parent = Parent;
while (parent != null) {
if (parent is ListViewWebPart) {
return (ListViewWebPart)parent;
}
parent
= parent.Parent;
}
return null;
}

You should know where I'm going with this now, but for completness I will show you the last piece of code below which restricts it to a single list instance:

private Guid TARGET_LIST_ID = new Guid ("0D9B9302-8599-4CE5-8695-1B95FE7378F1");

protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (!Page.IsPostBack) {
EnsureChildControls();
_action
.Visible = false;
ListViewWebPart listView = GetParentListViewWebPart();
if (listView != null) {
Guid listGuid = new Guid(listView.ListName);
if (TARGET_LIST_ID == listGuid) {
_action
.Visible = true;
}
}
}
}

Now that we have the list Guid that our control is being rendered for I will leave it as an exercise to the reader to restrict it to a given content type.

Why SharePoint doesn't provide this kind of functionality out of the box amazes me, but at least they provide a powerful API that you can get in there and work around it in a not so horribly kludgy way. Maybe SharePoint 2010 will include this :)

CREATING HIERARCHICAL MENUS WITH A CUSTOMACTION IN SHAREPOINT

It’s a fairly known technique to make use of CustomActions to add elements to the out-of-the-box user interface of SharePoint: you can add menu items to the Site Actions menu, you can add links on the Site Settings page, etc. The following piece of XML is the manifest of a feature that will add a new menu item to the Site Actions menu:

< Elements xmlns="http://schemas.microsoft.com/sharepoint/">
< CustomAction
Id="{B0B5A0CB-7FBE-4dd6-9B2A-2B1E1321B8F9}"
Location="Microsoft.SharePoint.StandardMenu"
GroupId="SiteActions"
Title="Dummy Menu Item">
< UrlAction
Url="/_layouts/dummy.aspx"/>
< /CustomAction>
< /Elements>

For a more detailed description of CustomActions, I recommend following articles:

Another variation on this technique is to provide a reference to a class, instead of having fixed UI element specified in the XML. The following piece of XML points to the class ListSettingsMenu in the DemoCustomAction assembly.

< Elements xmlns="http://schemas.microsoft.com/sharepoint/">
< CustomAction
Id="{42550415-FD08-4f1f-BAE6-93CCB2A2DE60}"
Location="Microsoft.SharePoint.StandardMenu"
GroupId="SiteActions"
ControlAssembly="DemoCustomAction"
ControlClass="DemoCustomAction.ListSettingsMenu">
< /CustomAction>
< /Elements>

The cool thing is that you now can write code that will render the UI element; it’s even possible to create a hierarchical menu. The following implementation of the ListSettingsMenu class, in combination with the XML from above, is adding one extra menu item to the Site Actions menu (List Settings). This new menu item will contain a sub menu item for every list on the site, these sub menu items will point to the settings pages of the corresponding lists. The ListSettingsMenu class inherits from the WebControl class, by overriding the CreateChildControls method, you can instantiate SubMenuTemplate andMenuItemTemplate instances, and add them to the Controls collection. An instance of the SubMenuItemTemplate class corresponds with a menu item that contains sub menu items. These sub menu items are instances of the MenuItemTemplate class. By setting the Text, Description and ImageUrl properties of these classes, you can specify how the menu items will be rendered in the SharePoint UI. The ClientOnClickNavigateUrl of the MenuItemTemplate class specifies the URL for the menu item itself.

namespace DemoCustomAction
{
public class ListSettingsMenu: System.Web.UI.WebControls.WebControl
{
protected override void CreateChildControls()
{
SubMenuTemplate listSettings = new SubMenuTemplate();
listSettings.Text = "List Settings";
listSettings.Description = "Manage settings for lists on this site";
listSettings.ImageUrl = "/_layouts/images/lg_ICASCX.gif";

foreach (SPList list in SPContext.Current.Web.Lists)
{
if (!list.Hidden)
{
MenuItemTemplate listItem = new MenuItemTemplate();
listItem.Text = list.Title;
listItem.Description = string.Format(
"Manage settings for {0}", list.Title);
listItem.ImageUrl = list.ImageUrl;

string url = string.Format(
"{0}/_layouts/listedit.aspx?List={{{1}}}",
SPContext.Current.Web.Url, list.ID.ToString());
listItem.ClientOnClickNavigateUrl = url;

listSettings.Controls.Add(listItem);
}
}

this.Controls.Add(listSettings);
}
}
}

To deploy all of this first of all the assembly (DLL) that contains the ListSettingsMenu should be built and copied either to the Global Assembly Cache or the BIN folder of the SharePoint site where you’d like to use it. Secondly the feature should be installed by copying the feature files and running STSADM -o installfeature -n featurename. Finally a SafeControl element must be added to the web.config, so the ListSettingsMenu control is marked as safe. If you forget the last step, you won’t get an error, but the extra menu item won’t be rendered. Here is a screenshot of the result:

The only caveat for this technique seems to be that you can’t use it to add a hierarchical menu in a EditControlBlock (ECB) CustomAction. The menu items of the ECB are rendered using Javascript. If you want to see a full blown example of what you can accomplish, check out the latest addition to the SmartToolsproject: the Enhanced Site Actions menu.