CSS & JS Resources

6/26/2023

By: Shaun Walker

In general the philosophy in Oqtane is to encapsulate the functionality of a module or theme within the software component itself, and not rely on other external workloads for orchestration. Therefore, the recommended approach for managing Stylesheets and JavaScript is to declare them internally within your module or theme so that they are discoverable by the framework.

In Oqtane 4.0 there are now 2 ways to do this depending on the functionality of your components and personal development preference.

The first way has been supported since Oqtane 1.0 and requires you to declare them within your Razor components using the Resources property. The Oqtane framework will utilize the Resources property at run-time to dynamically inject the required elements into the page.

(from Index.razor for the Blazor Theme in the Oqtane framework)

@code {
     public override List Resources => new List()
     {
          new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css", Integrity = "sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==", CrossOrigin = "anonymous" },
          new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
     new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" }
     };
}

There is one caveat to keep in mind when it comes to JavaScript resources. Blazor restricts JavaScript from being used directly, and requires that it must be used via JS Interop. If you are simply declaring a JavaScript library in your component (such as the Bootstrap JavaScript library that is used with Bootstrap CSS) then you simply need to include the Resources declaration described above and it will be added to your page automatically (by the OnAfterRenderAsync method which is included as part of the base class). However, if you also wish to invoke a JavaScript method from the included library, using JS Interop from within the same component, then there is obviously a timing issue to consider as the JavaScript library will need to be loaded before you can call its methods. The typical pattern for accomplishing this is as follows:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
     // register the JavaScript library declared in Resources
     await base.OnAfterRenderAsync(firstRender);
     if (!firstRender)
     {
          // call a method within a JavaScript library using JS Interop
          var interop = new Interop(JSRuntime);
          await interop.MethodName();
     }
}

In Oqtane 4.0 an additional method was added for declaring resources. Rather than requiring them to be repeated in every Razor component within a module or theme, it is now possible to declare them in the IModule or ITheme interface implementation - which indicates that they should be used for all Razor components which are part of your extension. Also note the use of the tilde character ("~") on the "~/Theme.css" reference which indicates that the framework should dynamically determine the path to this resource at run-time.

(from ThemeInfo.cs for the Oqtane Theme in the Oqtane framework)

public class ThemeInfo : ITheme
{
     public Theme Theme => new Theme
     {
          Name = "Oqtane Theme",
          Resources = new List()
          {
               new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.0/cyborg/bootstrap.min.css", Integrity = "sha512-jwIqEv8o/kTBMJVtbNCBrDqhBojl0YSUam+EFpLjVOC86Ci6t4ZciTnIkelFNOik+dEQVymKGcQLiaJZNAfWRg==", CrossOrigin = "anonymous" },
               new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
               new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" }
          }
     };
}

The IHostResources interface was introduced in Oqtane 1.0.4 in 2020. It was intended to be a short-term work-around to allow developers to dynamically add static resource references for CSS and JavaScript libraries into the page head during the initial server render. The problem with this interface was that it was not designed with multi-tenancy in mind - which basically meant that if you used it, the resources specified would be included in every page in every site in your entire installation. In addition, it relied on reflection which meant that every server-side page request had to iterate through the assemblies loaded into the app domain to determine if they implemented the interface - which was not optimal for performance. In Oqtane 4.0 this interface has been deprecated and in those rare cases where the above methods for declaring resources are not sufficient (ie. you need for JavaScript to be injected server-side and included for every page within a site) you can use the ResourceLevel.Site property specification within the IModule or ITheme implementation (note that this feature is not supported for resources declared within a Razor component).

public class ModuleInfo : IModule
{
     public Module Module => new Module
     {
          Name = "Sample Module",
          Resources = new List()
          {
               new Resource { ResourceType = ResourceType.Script, Url = "~/Module.js", Level = ResourceLevel.Site }
          }
     };
}


Do You Want To Be Notified When Blogs Are Published?
RSS