Webdesigners vs. SharePoint: the body id

, , , , ,

Body id is a cool webdesign trick that allows designers to easily alter the layout of a page using nothing more than a single property and some CSS styling. Using the body id you can define one HTML page structure for the whole site and then, by changing the single value of the body id attribute, you can create new experiences by styling the different pieces of the page in a totally different way. While it sounds very easy and it is very easy with HTML, this trick can get very challenging when used with SharePoint.

Anatomy of a body id

Consider the following HTML:

<div id="header" class="section">
Header
</div>
<div id="navigation" class="section">
Navigation
</div>
<div id="main" class="section">
Body
</div>
<div id="subpane" class="section">
Subpane
</div>

Using the body id trick you can make it easily look like

Home Page layout

or like

Content Page layout

All that you have changed between the first and the second UI is the value of the body id attribute. Impressive, isn’t it?

How does it work? The whole thing consists of two pieces: the id attribute added to the body tag:

<body id="content">

and CSS that formats the page layout using the body id information:

.section
{
    border: 3px solid red;
}

body#home #header { display: none; }

body#home #navigation
{
    width: 47%;
    float: left;
    height: 200px;
}

body#home #main
{
    width: 47%;
    margin-bottom: 0.5em;
    height: 200px;
    float: right;
}

body#home #subpane
{
    clear: both;
}

body#content #header
{
    font-size: xx-large;
    margin-bottom: 0.5em;
}

body#content #navigation
{
    float: left;
    width: 20%;
    height: 200px;
    margin-right: 0.5em;
}

body#content #main
{
    float: right;
    height: 200px;
    width: 76%;
    margin-bottom: 0.5em;
}

body#content #subpane
{
    clear: both;
}

Using the body id trick simplifies creating different layouts making it very efficient and easy to manage. And using the information obtained from the Content Management System (CMS) you can dynamically define the body id and change the layout without a single change in the page markup.

The SharePoint challenge

Quite often the translation of webdesign to HTML is separated from the CMS implementation and is often done by different teams. Not surprisingly both processes are very different and require different sets of skills and experience. And this is where the SharePoint challenge has its origins: webdesigners have no clue how SharePoint works and what its limitations are and once the HTML is done, it’s too late for SharePoint developers to throw it around to make it fit into SharePoint. Because webdesigners don’t know SharePoint’s boundaries, they do what’s most efficient for them, not knowing that they will complicate the implementation of their markup into SharePoint a lot. So why can’t the HTML with body id be implemented in SharePoint in the first place?

As SharePoint is built on top of ASP.NET, let’s have a look at some possibilities of how to include dynamic values for attributes.

Wrap it up

Probably the very first solution that you might think of is to create a custom webcontrol and set the value of the id attribute during its lifecycle. This could be done using the following code snippet:

[ParseChildren(false)]
public class Body : WebControl
{
    public Body() : base(HtmlTextWriterTag.Body)
    {

    }

    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);

        Attributes["id"] = GetBodyId();
    }

    private static string GetBodyId()
    {
        string bodyId = "content";

        if (SPContext.Current != null && SPContext.Current.ListItem != null && PublishingPage.IsPublishingPage(SPContext.Current.ListItem))
        {
            PublishingPage page = PublishingPage.GetPublishingPage(SPContext.Current.ListItem);
            if (page.Layout.Name.Equals("WelcomeSplash.aspx", StringComparison.InvariantCultureIgnoreCase))
            {
                bodyId = "home";
            }
            else
            {
                bodyId = "content";
            }
        }

        return bodyId;
    }
}

In order to use it you would register the control with your Master Page and then replace the default body tag with your control:

<MyControls:Body runat="server">...

While it does the job, and outputs a beautiful body tag with a dynamic ID attribute, it introduces one serious problem. If you open your Master Page in SharePoint Designer in Design View all that you will see is a blank screen:

Wrapping the contents of the Master Page’s body in a custom control brakes the Design View in SharePoint Designer

The first thing that you might think, is that the control requires some extra code that will allow SharePoint Designer to render the contents of the page, just like it does with a regular ASP.NET Panel. Unfortunately, that doesn’t help either. To check this, you can create a new control and make it inherit from the Panel control. The same behavior occurs in SharePoint Designer: the Design View is not being displayed. We’ve just managed to break the platform.

Express yourself

When I saw the default behavior of SharePoint Designer when wrapping the contents of the body in a custom control, I almost immediately thought of another method to get the body id rendered: Expression Builders.

Expression Builders are a great, and it turns out a not-that-well-known, part of ASP.NET that allows you to dynamically set values of server controls in ASP.NET. If you’ve worked with SharePoint a little, you’re probably familiar with at least two of SharePoint’s Expression Builders: Resources and SPUrl.

Comparing to a server control, an Expression Builder is a little more complex to build. The complexity has to do with the Code Expression that has to be returned during the compilation or parsing process. This Code Expression will be executed every time the Expression Builder is requested and will return some value based on the requested property on some other condition.

The Expression Builder to set the body id could look something like this:

public class BodyEB : ExpressionBuilder
{
    private const string BodyIdId = "BodyId";

    public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
    {
        CodeExpression[] codeExpression =
        {
            new CodePrimitiveExpression(entry.Expression.Trim()),
            new CodeTypeOfExpression(typeof(String))
        };

        return new CodeCastExpression(typeof(String),
            new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression(typeof(BodyEB)), "GetEvalData", codeExpression));
    }

    public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
    {
        return GetEvalData(entry.Expression);
    }

    public override bool SupportsEvaluate
    {
        get
        {
            return true;
        }
    }

    public static object GetEvalData(string expression)
    {
        string value = null;

        switch (expression)
        {
            case BodyIdId:
                value = GetBodyId();
                break;
        }

        return value;
    }

    private static string GetBodyId()
    {
        string bodyId = "content";

        if (SPContext.Current != null && SPContext.Current.ListItem != null && PublishingPage.IsPublishingPage(SPContext.Current.ListItem))
        {
            PublishingPage page = PublishingPage.GetPublishingPage(SPContext.Current.ListItem);
            if (page.Layout.Name.Equals("WelcomeSplash.aspx", StringComparison.InvariantCultureIgnoreCase))
            {
                bodyId = "home";
            }
            else
            {
                bodyId = "content";
            }
        }

        return bodyId;
    }
}

Then in the Master Page you would modify the body tag to include the call to the Expression Builder:

<body id="<%$UI:BodyId %>" runat="server" scroll="no" onload="if (typeof(_spBodyOnLoadWrapper) != 'undefined') _spBodyOnLoadWrapper();" class="nightandday">

Because Expression Builders can only be used with server controls, you have to add runat=”server” to the body tag. Save the changes, hit F5 in the browser and guess what: it doesn’t work. It turns out that you cannot set the control’s ID using an Expression Builder. So much for a body id. What about a body class? Perhaps not perfect but very similar to the body id idea? Unfortunately yet another error: this time ASP.NET says that the onload attribute is not allowed.

The very last thing that you could do, to get this done, is to post process the rendered HTML and add the body ID there. Although it would work, such approach would have very poor performance and could cause your whole site to become very slow.

So is there any other way to make this happen?

Introducing: Wrap it up v2

One way that you could get this done is to use an extra div to set the body class:

<div runat="server" class="<%$UI:BodyId %>">

In modern browsers you could use the :first-child CSS selector to refer to the wrapper div:

body div.home:first-child { ... }

And if you need to support older browsers as well, you could add an ID to your control.

Important

The ID that you specify is not the ID that will end up in the generated HTML. As the div is a server control (runat=”server”) the ID will be prepended with the ID of the div’s parent control to define it uniquely. So very likely you will end up with ID of ctl00_yourId

Although this method is not perfect it allows you to use a wrapper to alter your page layout dynamically. And because a div doesn’t have a semantic meaning it doesn’t obstruct the content of the page.

Summary

There are a lot of tricks that webdesigners use to deliver rich and dynamic experiences. When working with a Content Management System it is however important to take a look at its boundaries and to check if everything that the designer had in mind, is achievable in a way that won’t break the CMS. Using a body id is an example of such trick: although the idea seems to be very simple when working with static HTML and CSS, it can get really challenging when combined with SharePoint.

Possibly related posts

13 Responses to “Webdesigners vs. SharePoint: the body id”

  1. Marc D Anderson Says:

    Of course, there's yet another option. Rather than using server-side controls to render the HTML, you can use client-side scripts to manipulate the DOM upon page load. Different toolset, but you're able to accomplish similar things.

    Both approaches have their strengths and weaknesses. I'd like to see more developers learn to straddle the two approaches, using the right one for the specific tasks at hand.

    M.

  2. Waldek Mastykarz Says:

    @Marc: using client-side scripts is definitely an option worth considering. Its biggest drawback is the dependency on the presence of JavaScript which might be disabled. But if your scenario allows you to use JavaScript for that purpose I would absolutely agree that using it, would be way easier than using any other server control.

  3. Marc D Anderson Says:

    Have you ever run into any more than an isolated instance of Javascript being disabled? I think years ago that (or just a browser not being capable) might have been a possibility but I'd be extremely surprised to run into it today.

    M.

  4. Waldek Mastykarz Says:

    @Marc: although I haven't run into it personally, I've heard about some enterprises disabling JavaScript. Difficult to tell whether this is true or false. http://www.w3schools.com/browsers/browsers_stats.asp shows that only a little percentage has JavaScript disabled. Still one should really consider this depending on his audience.

  5. Marc Anderson Says:

    Knowing the audience is critical for any successful deployment, for sure, and browser capabilities are a big part of that. Those startistics are an interesting reference point. They are generated from W3Schools' log files so obviously our mileage may vary.

    M.

  6. Marc Anderson Says:

    After all that, I forgot to say that this is a great article, Waldek!

    M.

  7. Waldek Mastykarz Says:

    Thanks, Marc :D

  8. shannon Says:

    I wanted to mention that the client not having javascript enabled is not a viable use case when it comes to sharepoint, which requires javascript to work correctly.

  9. Waldek Mastykarz Says:

    @shannon: this is very true for a SharePoint collaboration solution, but can be a totally viable case for a public facing site where you don't know what kind of configuration the visitor is using.

  10. Jaap Vossers Says:

    I'm wondering if it would be possible to include a asp:Literal control inside the body id attribute (wrap in single quotes) and set the Text property for the Literal using the expressionbuilder – and don't set the body runat=server.

    Alternatively you could try referencing a static method like <body id=''>, also without runat=server

    Both methods would mean you don't need the extra div.

    I think I have tried these techniques before – they might just work :)

  11. Jaap Vossers Says:

    previous message got messed up.

    should have a call to a static method inside the single quotes, wrapped in

  12. Waldek Mastykarz Says:

    @Jaap: while you could put a server control, like Literal in an attribute, I don't think that SharePoint Designer would understand it. It seems more of a hack than a neat solution. The problem with static method is, that in-line code is not supported for customized pages, so as soon as someone modifies the Master Page through SharePoint Designer, the code break the page.

  13. ID´s on the body tag | Branding SharePoint Says:

    [...] Curious what you can do with an ID on the body element? You could change the layout depending on what site or subsite you are. I also used it to change some styling only when in edit mode. A very powerfull trick. Waldek Mastykarz wrote a nice article about it here. [...]

Leave a Reply

Security Code:

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS
Copyright © 2007 - 2010 Waldek Mastykarz

Creative Commons License