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 to render JavaScript code at the bottom of the page that each of my 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"); }