Themes - Code Structure of a Theme
Internally a theme only needs the following 4 things:
- An own namespace such as
YourCompany.Themes.YourTheme
. - A
ThemeInfo.cs
file in that namespace which implementsOqtane.Themes.ITheme
.
This file contains information about the theme like name, author, version, etc. - A theme file like
MyTheme.razor
which is the main thing shown to the user. - A container file like
Container.razor
which is the main wrapper around a module.
Warning
It is crucial that the namespace of the theme is unique, and that all these core elements (ThemeInfo, Theme, Container) are in exactly this namespace.
Example from the Oqtane Arsha
Arsha is a sample theme that comes with Oqtane. You can find the git repo here.
ThemeInfo.cs
The ThemeInfo.cs file is necessary to provide information about the theme. In the code you can see it contains
- The
namespace
of the theme - must be unique, and must match the theme/container namespace - the
Name
of the theme - displayed to the user - the
Version
of the theme - the
PackageName
- used for installation / differentiation - the optional
ThemeSettingsType
(class name) of the theme settings - the optional
ContainerSettingsType
(class name) of the container settings - a list of
Resources
- CSS and JS files that are loaded when the theme is used
note: theResources
can also be specified in eachTheme.razor
file
using System.Collections.Generic;
using Oqtane.Models;
using Oqtane.Themes;
using Oqtane.Shared;
namespace Oqtane.Theme.Arsha
{
public class ThemeInfo : ITheme
{
public Models.Theme Theme => new Models.Theme
{
Name = "Arsha",
Version = "1.0.0",
PackageName = "Oqtane.Theme.Arsha",
ThemeSettingsType = "Oqtane.Theme.Arsha.ThemeSettings, Oqtane.Theme.Arsha.Client.Oqtane",
ContainerSettingsType = "Oqtane.Theme.Arsha.ContainerSettings, Oqtane.Theme.Arsha.Client.Oqtane",
Resources = new List<Resource>()
{
// Google Fonts
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i%7CRaleway:300,300i,400,400i,500,500i,600,600i,700,700i%7CPoppins:300,300i,400,400i,500,500i,600,600i,700,700i" },
// Vendor CSS Files
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/assets/vendor/aos/aos.css" },
// ...rest of files omitted for brevity
// Template Main CSS File
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/assets/css/style.css" },
// Vendor JS Files
new Resource { ResourceType = ResourceType.Script, Url = "~/assets/vendor/aos/aos.js", Location = ResourceLocation.Body },
// ...rest of files omitted for brevity
// Template Main JS File
new Resource { ResourceType = ResourceType.Script, Url = "~/assets/js/main.js", Location = ResourceLocation.Body, Reload = true, RenderMode = RenderModes.Static },
new Resource { ResourceType = ResourceType.Script, Url = "~/assets/js/interop.js", Location = ResourceLocation.Body, RenderMode = RenderModes.Interactive }
}
};
}
}
The Theme File
The Theme.razor file is the main file that is shown to the user.
It contains the HTML structure of the theme and can include other Blazor components. Important things to know:
- The
@namespace
directive must match the namespace of the theme - The
@inherits
directive must inherit fromThemeBase
or a class that inherits fromThemeBase
- The
@inject
directive can be used to inject services likeNavigationManager
orISettingService
- The
@code
block can contain C# code that is executed when the component is rendered - The HTML part - which is the HTML inside the
<body>
tag (without<head>
etc.). This "html" can be- normal HTML such as
div
,section
,h1
,p
,img
etc. - Razor variables such as
@PageState.Page.Name
- Razor data from functions such as
@NavigateUrl()
- Razor statements such as
@if
or@foreach
- Blazor components such as
<NavMenu />
or<Login />
- normal HTML such as
@namespace Oqtane.Theme.Arsha
@inherits ThemeBase
@inject NavigationManager NavigationManager
@inject ISettingService SettingService
<!-- ======= Header ======= -->
<header id="header" class="@_headerclass">
<div class="container d-flex align-items-center">
<!-- omited for brevity -->
<nav id="navbar" class="navbar">
<NavMenu />
</nav>
<div class="mx-3 controls-group"><UserProfile /> <Login /> <ControlPanel /></div>
</div>
</header>
<!-- End Header -->
@if (!string.IsNullOrEmpty(_hero))
{
<!-- ======= Hero Section ======= -->
@((MarkupString)_hero)
<!-- End Hero -->
<!-- ======= Main ======= -->
<main id="main">
<Pane Name="@PaneNames.Default" />
</main>
<!-- End #main -->
}
else
{
<!-- ======= Main ======= -->
<main id="main">
<!-- ======= Breadcrumbs ======= -->
<section class="breadcrumbs">
<div class="container">
<h2 class="mt-2">@PageState.Page.Name</h2>
</div>
</section><!-- End Breadcrumbs -->
<section class="inner-page">
<div class="container">
<Pane Name="@PaneNames.Default" />
</div>
</section>
</main>
<!-- End Main -->
}
<!-- ======= Footer ======= -->
@((MarkupString)_footer)
<!-- End Footer -->
<a href="@NavigateUrl()" class="back-to-top d-flex align-items-center justify-content-center" data-enhance-nav="false"><i class="bi bi-arrow-up-short"></i></a>
@code {
public override string Name => "Default";
public override string Panes => PaneNames.Default;
private string _hero = "";
private string _headerclass = "";
private string _footer = "";
protected override async Task OnParametersSetAsync()
{
// omitted for brevity
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// omitted for brevity
}
private string DefaultFooter
{
get
{
return
"<footer id=\"footer\">\n" +
" <div class=\"footer-newsletter\">\n" +
<!-- omitted for brevity -->
" </div>\n" +
"</footer>\n";
}
}
}
The Container File
The Container.razor file is the main wrapper around a module. Here's what you should know:
- The
@namespace
directive must match the namespace of the theme - The
@inherits
directive must inherit fromContainerBase
or a class that inherits fromContainerBase
- The
@inject
directive can be used to inject services likeISettingService
- The
@code
block can contain C# code that is executed when the component is rendered - The HTML parts with the same possibilities as the theme above.
@namespace Oqtane.Theme.Arsha
@inherits ContainerBase
@inject ISettingService SettingService
@if (_title)
{
@if (_animate)
{
<section>
<div class="container" data-aos="fade-up">
<ModuleActions />
@if (ModuleState.Title != "-")
{
<div class="section-title">
<h2>@ModuleState.Title</h2>
</div>
}
<div class="row">
<ModuleInstance />
</div>
</div>
</section>
}
else
{
<!-- omitted for brevity -->
}
}
else
{
<!-- omitted for brevity -->
}
@code {
private bool _title = true;
private bool _animate = false;
protected override void OnParametersSet()
{
// omitted for brevity
}
}
Blazor Components
In addition to the required minimum files, you can also include other Blazor components. Here's an example of the NavMenu.razor.
It doesn't do much, but check if the menu should show, and then calls the NavMenuItems
component.
@namespace Oqtane.Theme.Arsha
@inherits MenuBase
@if (MenuPages.Any())
{
<NavMenuItems ParentPage="null" Pages="MenuPages" />
<i class="bi bi-list mobile-nav-toggle"></i>
}
The NavMenuItems.razor is way more complex, as it recursively calls itself to render the menu items.
@namespace Oqtane.Theme.Arsha
@inherits MenuItemsBase
<ul>
@foreach (var childPage in GetChildPages())
{
var _attributes = new Dictionary<string, object>();
_attributes.Add("href", GetUrl(childPage));
var _target = GetTarget(childPage);
if (!string.IsNullOrEmpty(_target))
_attributes.Add("target", _target);
if (!childPage.HasChildren)
{
<!-- omitted for brevity -->
}
else
{
<li class="dropdown">
<a @attributes="_attributes"><span>@childPage.Name</span>
@if (childPage.Level == 0)
<i class="bi bi-chevron-down"></i>
else
<i class="bi bi-chevron-right"></i>
</a>
<NavMenuItems ParentPage="childPage" Pages="Pages" />
</li>
}
}
</ul>
Daniel Mettler, @iJungleboy
[MS MVP, Oqtane Core Team]
Content Management Expert, Chief Architect of 2sxc and cre8magic.
Forged in the jungles of Indonesia, lives in Switzerland , loves Oqtane 🩸 & 2sxc 💜.
LinkedIn | Discord: @iJungleboy | Twitter: @iJungleboy | Github: @iJungleboy