Kentico 12 MVC: Get List of Widgets Used On A Page

There are times when you need to know what widgets are being used on a page. In my case, I needed to know this information in order to render JavaScript code at the bottom of the page that each of my individual widgets depends on.

Why don't I just place all the JavaScript code my site and widgets use in one file? Loading one large JavaScript file isn't the best approach for page performance. Instead, I use LabJS to dynamically load scripts in specific execution order without blocking other resources. So if I created a Carousel widget in Kentico, I would only load the JavaScript plugin if added to the page.

I'll use my JavaScript scenario as a basis for demonstrating the way to list out widgets used in a page.

If we delve into the CMS_Document table, Kentico uses the "DocumentPageBuilderWidgets" field that stores a JSON structure consisting of a list of all the widgets and their property values. All we are interested in is the type property.

Let's get to the code.

Controller - SharedController

I created a SharedController class containing a GenerateWidgetJavascript() PartialViewResult. This will convert the JSON from the "DocumentPageBuilderWidgets" field into a JSON Object to then be queried (using SelectTokens) to select every iteration of the type field in the hierarchy.

/// <summary>
/// Get widget used on a page to render any required JavaScript.
/// </summary>
/// <returns></returns>
public PartialViewResult GenerateWidgetJavascript()
{
    List<string> widgetTypes = new List<string>();

    if (Page.GetStringValue("DocumentPageBuilderWidgets", string.Empty) != string.Empty)
    {
        JObject pageWidgetJson = JObject.Parse(Page.GetStringValue("DocumentPageBuilderWidgets", string.Empty));

        if (pageWidgetJson != null)
            widgetTypes = pageWidgetJson.SelectTokens("$.editableAreas[*].sections[*].zones[*].widgets[*].type").Select(jt => jt.ToString().Substring(jt.ToString().LastIndexOf('.') + 1)).Distinct().ToList();
    }

    return PartialView("Widgets/_PageWidgetJs", widgetTypes);
}

Additional manipulation is carried out on the type field using LINQ to return a distinct set of results, as there might be a case where the same widget is used multiple times on a page. Since I name all my widgets in the following format - <SiteName>.<WidgetGroup>.<WidgetName>, I am only interested in the <WidgetName>. For example, my widget would be called "SurinderSite.Layout.Carousel". The controller will simply output "Carousel".

To avoid confusion in my code snippet, it's worth noting I use a Page variable. This contains information about the current page and is populated in my base controller. It has a type of TreeNode. You can see my approach to getting the current page information in this post.

Partial View - _PageWidgets

The most suitable place to add my widget dependent JavaScript is in the /View/Shared/Widgets directory - part of the recommended Kentico project structure.

All we need to do in the view is iterate through the string collection of widget types and have a bunch of if-conditions to render the necessary JavaScript.

@model List<string>

@if (Model.Any())
{
    <script>
        @foreach (string widgetType in Model)
        {
            if (widgetType == "Carousel")
            {
                <text>
                    $LAB
                        .script("/resources/js/plugins/slick.min.js")
                        .script("/resources/js/carousel.min.js").wait(function () {
                            {
                                FECarousel.Init();
                            }
                        });
                </text>
            }
        }
    </script>
}

Layout View

The Layout view will be the best place to add the reference to our controller in the following way:

@{ Html.RenderAction("GenerateWidgetJavascript", "Shared"); }