Automatically generating a hierarchical Title in <title> element

Recently while working on an Internet facing web site for one of our customers I thought of creating a control which would automatically create a hierarchical Title in the <title> element, like: Site Collection - Current Site - Current Page. Standard SharePoint 2007 allows you to define the title on the Master Page and within a Page Layout. Default SharePoint 2007 displays the Page Title in the <title> element. As I've been recently researching the accessibility issues I have noticed that such a behavior can cause loosing the context - especially if the visitor is vision impaired. Secondly it might cause search engines indexing a page and not linking it to the organization (Site Collection) or for example a division within it (Site). You could solve it using the standard features like displaying the Site's Title using <SharePoint:ProjectProperty> but still it wouldn't provide me the flexibility I wanted it to have. That's why I have decided to make a control which would automatically generate a title based on the existing hierarchy.

The requirements

The most important is generating a title consisting of information from the three levels: Site Collection, Site and Page the user is currently on. As you might want to hide the Site Collection title in some cases it should be also optional whether it should be displayed or not. In my example I have used a minus as a separator: however it should be possible to set the separator without rewriting the control. The control should also be intelligent enough to be able to distinct whether the user is on the Root Web and Default Page. Another option is the possibility to reverse the title so it would display Page - Site - Site Collection instead Site Collection - Site - Page: some customers believe that it improves the readability and context. Personally I would love to leave this choice to them. Last but not least the control have to work with anonymous access - it's definitely something you should keep in mind from the very beginning as it influences the way we will be obtaining site information.

The work

First of all let's define the properties we will need:

private string _Separator;  
public string Separator  
{
  get { return _Separator; }
  set { _Separator = value; }
}

private bool _Reversed;  
public bool Reversed  
{
  get { return _Reversed; }
  set { _Reversed = value; }
}

private bool _SuppressSiteCollectionTitle;  
public bool SuppressSiteCollectionTitle  
{
  get { return _SuppressSiteCollectionTitle; }
  set { _SuppressSiteCollectionTitle = value; }
}

Then let's implement the logic. As it's quite straight forward let's put it all simply in the Render method. Furthermore it will provide us with the required control over the rendered output as we don't want any extra elements rendered with the title.

protected override void Render(HtmlTextWriter writer)  
{
  List<string> items = new List<string>(3);
  Guid siteId = SPContext.Current.Site.ID;
  Guid webId = SPContext.Current.Web.ID;
}

First of all we define some variables we are going to need later on: a list where we will store the pieces of the title (it can contain default 3 only 3 items because we will be needing only the titles of the Site Collection, Site and Page). We will need the ID's of both Site Collection and Site to open new instances with elevated privileges. As we need to obtain the titles of both of these for anonymous users as well we need to run this piece of code with elevated privileges.

protected override void Render(HtmlTextWriter writer)  
{
  List<string> items = new List<string>(3);
  Guid siteId = SPContext.Current.Site.ID;
  Guid webId = SPContext.Current.Web.ID;

  SPSecurity.RunWithElevatedPrivileges(delegate()
  {
    SPSite elevatedSite = new SPSite(siteId);
    SPWeb elevatedWeb = elevatedSite.OpenWeb(webId);
    if (!SuppressSiteCollectionTitle)
      items.Add(elevatedSite.RootWeb.Title);
    if (!SPContext.Current.Web.IsRootWeb ||
      SuppressSiteCollectionTitle)
      items.Add(SPContext.Current.Web.Title);

    if (String.Compare(SPContext.Current.ListItem.Url,
        PublishingWeb.GetPublishingWeb(elevatedWeb).
        DefaultPage.Url, true) != 0)
      items.Add(SPContext.Current.ListItem.Title);

    elevatedWeb.Dispose();
    elevatedSite.Dispose();
  });
}

First of all we create new instances of the Site Collection and Site as we will be needing them to obtain the titles. Then we make the first check to find out whether the user has chosen to suppress the Site Collection's title part. As we go further we need to obtain the title of the Site but only if the visitor is not currently on the root site or if the developer has chosen to suppress the Site Collection's title. Without this check you would get the Site Collection's title twice while being on the home page of the web site.

The last piece of the title is the page title. It should be displayed only if the current page is not a welcome page: in this case the page's title is equal to the Site's title. For the title comparison I'm using the Compare method instead String.ToLower() != String.ToLower() as it creates new instance of the compared strings in the memory.

We still one requirement left:

protected override void Render(HtmlTextWriter writer)  
{
  List<string> items = new List<string>(3);
  Guid siteId = SPContext.Current.Site.ID;
  Guid webId = SPContext.Current.Web.ID;

  SPSecurity.RunWithElevatedPrivileges(delegate()
  {
    SPSite elevatedSite = new SPSite(siteId);
    SPWeb elevatedWeb = elevatedSite.OpenWeb(webId);
    if (!SuppressSiteCollectionTitle)
      items.Add(elevatedSite.RootWeb.Title);
    if (!SPContext.Current.Web.IsRootWeb ||
      SuppressSiteCollectionTitle)
      items.Add(SPContext.Current.Web.Title);

    if (String.Compare(SPContext.Current.ListItem.Url,
        PublishingWeb.GetPublishingWeb(elevatedWeb).
        DefaultPage.Url, true) != 0)
      items.Add(SPContext.Current.ListItem.Title);

    elevatedWeb.Dispose();
    elevatedSite.Dispose();
  });

  if (Reversed)
    items.Reverse();

  string pageTitle = String.Empty;
  foreach (string item in items)
  {
    if (String.IsNullOrEmpty(item))
      continue;

    if (pageTitle.Length > 0)
      pageTitle += Separator;
    pageTitle += item;
  }

  writer.Write(pageTitle);
}

To handle the reversed order of the title we use the Reverse method of a List generic collection. All we need to do is to put the title together and render it.

Using this control will allow you to display consistent titles across the Site Collection and will provide context information to the visitors. The best way to use it is to put it in the Master Page between the <title></title> elements. What you need to do is to hide the PlaceHolderPageTitle. You can't remove it from the Master Page and what's even more important you can't remove it from the Page Layouts as well. I have explained it why in one of my previous posts.

Comments

comments powered by Disqus