Published on

Adding a class to HTML when a custom widget is on the page

Authors

The scenario where you need to change styling on the site, or inform other widgets based on some other widget on the page is so common in Sitefinity. It can quite easily be done client-side as well, something like…

if($(".my-toolbar").length > 0){
  $("body").addClass("toolbar-exists");
}

Now you could style the rest of the page based on the idea that a toolbar now exists. Maybe your header needs extra margins, or some element needs to be absolute, or heck maybe it’s just a 3/9 grid that has a toolbar.

The issue with clientside though is the page has to render, then the script has to run, and you’re going to see some popping of elements as that class is applied to body and it’s rules take effect.

The PROPER way to do this is to put it into the Server-Side so the page renders with that class. Let me prefix this by saying it’s something Sitefinity should add to their API, something like the ICustomWidgetVisualization where I can just tell it the class name I want, and if it’s on the page it’ll render in there. Sitefinity team, please take note, this would be really quite a great API feature for devs, solve a lot of problems for juniors or people new to the platform.

Anyway, here’s what I came up with.

Inspiration came from the javascript feather widget which was somehow able to render content in the header or bottom of the page. You can checkout the code over on the (old?) git repo. The widget hooks into the PagePreRender to inject it into those places!.. so that means we can also hijack the rendering!

Lets start by adding this to your Controller Index method somewhere

    var context = Telerik.Sitefinity.Services.SystemManager.CurrentHttpContext;
    if(context != null){
        var page = context.CurrentHandler.GetPageHandler();

        if (page != null)
        {
            page.PreRenderComplete += this.PagePreRenderCompleteHandler;
        }
    }

GetPageHandler() is an extension method so go ahead and add this using clause as well

using Telerik.Sitefinity.Frontend.Mvc.Infrastructure;

Now here’s the handler we’re going to use

        /// <summary>
        /// Handler called when the Page's PreRenderComplete event is fired.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
        private void PagePreRenderCompleteHandler(object sender, EventArgs e)
        {
            try
            {
                var widgetClassName = "my-toolbar";
                var page = (Page)sender;
                foreach(Control c in page.Controls)
                {
                    if(c.GetType().Name == "MvcMasterPage")
                    {
                        var layout = c;
                        foreach(var child in layout.Controls)
                        {
                            if(child is LiteralControl)
                            {
                                var literal = (LiteralControl)child;
                                if (literal.Text.Contains("<body") && !literal.Text.Contains(widgetClassName))
                                {
                                    literal.Text = literal.Text.Replace($"class=\"", $"class=\"{widgetClassName} ");
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

Basically we’re finding the root Layout, then looping through it’s controls to find the one that contains the body element, and then we’re just hijacking the class name to append our widget class name (if it doesn’t already exist).

IMPORTANT NOTE

For this to work, your <body element in your \ResourcePackages\ThemeName\Mvc\Views\Layouts\layoutname.cshtml needs to have class="" applied, even if there’s nothing in it.

   <!-- At a bare minimum because we need to hijack it -->
   <body class="">