support for module header and footer content

This commit is contained in:
sbwalker 2025-05-14 12:18:37 -04:00
parent 9000f05961
commit 57d443be8d
11 changed files with 147 additions and 22 deletions

View File

@ -97,6 +97,23 @@
</div>
</div>
</div>
<br />
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="header" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
<div class="col-sm-9">
<textarea id="header" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="footer" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
<div class="col-sm-9">
<textarea id="footer" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
</div>
</div>
</div>
</Section>
}
</TabPanel>
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
@ -144,6 +161,8 @@
private string _pane;
private string _containerType;
private string _allPages = "false";
private string _header = "";
private string _footer = "";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
@ -167,37 +186,47 @@
protected override async Task OnInitializedAsync()
{
SetModuleTitle(Localizer["ModuleSettings.Title"]);
_title = ModuleState.Title;
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
_pane = ModuleState.Pane;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (ModuleState.ModuleDefinition != null)
{
_module = ModuleState.ModuleDefinition.Name;
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
_pageId = pagemodule.PageId.ToString();
_title = pagemodule.Title;
_pane = pagemodule.Pane;
_containerType = pagemodule.ContainerType;
if (string.IsNullOrEmpty(_containerType))
{
_containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType;
}
_header = pagemodule.Header;
_footer = pagemodule.Footer;
_effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate);
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
_allPages = pagemodule.Module.AllPages.ToString();
createdby = pagemodule.Module.CreatedBy;
createdon = pagemodule.Module.CreatedOn;
modifiedby = pagemodule.Module.ModifiedBy;
modifiedon = pagemodule.Module.ModifiedOn;
_permissions = pagemodule.Module.PermissionList;
if (pagemodule.Module.ModuleDefinition != null)
{
_module = pagemodule.Module.ModuleDefinition.Name;
_permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames;
if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
@ -218,7 +247,7 @@
}
else
{
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error);
}
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
@ -270,10 +299,12 @@
{
pagemodule.ContainerType = string.Empty;
}
pagemodule.Header = _header;
pagemodule.Footer = _footer;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.PermissionList = _permissionGrid.GetPermissionList();

View File

@ -189,4 +189,19 @@
<data name="ModuleSettings.Title" xml:space="preserve">
<value>Module Settings</value>
</data>
<data name="Header.Text" xml:space="preserve">
<value>Header:</value>
</data>
<data name="Header.HelpText" xml:space="preserve">
<value>Optionally provide content to be injected above the module instance</value>
</data>
<data name="Footer.Text" xml:space="preserve">
<value>Footer:</value>
</data>
<data name="Footer.HelpText" xml:space="preserve">
<value>Optionally provide content to be injected below the module instance</value>
</data>
<data name="ModuleContent.Heading" xml:space="preserve">
<value>Content</value>
</data>
</root>

View File

@ -1,6 +1,7 @@
@namespace Oqtane.UI
@inject SiteState SiteState
@((MarkupString)ModuleState.Header)
@if (_comment != null)
{
@((MarkupString)_comment)
@ -13,6 +14,7 @@
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
}
}
@((MarkupString)ModuleState.Footer)
@code {
[CascadingParameter]
@ -23,6 +25,8 @@
private bool _prerender;
private string _comment;
private string _header;
private string _footer;
protected override void OnParametersSet()
{
@ -39,11 +43,16 @@
}
_comment += " -->";
_header = ModuleState.Header;
_footer = ModuleState.Footer;
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Interactive)
{
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
// please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components
PageState.Site.Pages = new List<Page>();
ModuleState.Header = string.Empty;
ModuleState.Footer = string.Empty;
}
}

View File

@ -76,6 +76,8 @@ namespace Oqtane.Controllers
module.ContainerType = pagemodule.ContainerType;
module.EffectiveDate = pagemodule.EffectiveDate;
module.ExpiryDate = pagemodule.ExpiryDate;
module.Header = pagemodule.Header;
module.Footer = pagemodule.Footer;
module.ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName));

View File

@ -246,6 +246,10 @@ namespace Oqtane.Controllers
pagemodule.Pane = pm.Pane;
pagemodule.Order = pm.Order;
pagemodule.ContainerType = pm.ContainerType;
pagemodule.EffectiveDate = pm.EffectiveDate;
pagemodule.ExpiryDate = pm.ExpiryDate;
pagemodule.Header = pm.Header;
pagemodule.Footer = pm.Footer;
_pageModules.AddPageModule(pagemodule);
}

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.06.01.03.02")]
public class AddModuleHeaderFooter : MultiDatabaseMigration
{
public AddModuleHeaderFooter(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase);
pageModuleEntityBuilder.AddMaxStringColumn("Header", true);
pageModuleEntityBuilder.AddMaxStringColumn("Footer", true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -442,6 +442,8 @@ namespace Oqtane.Repository
pageModule.Pane = (string.IsNullOrEmpty(pageTemplateModule.Pane)) ? PaneNames.Default : pageTemplateModule.Pane;
pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order;
pageModule.ContainerType = pageTemplateModule.ContainerType;
pageModule.Header = pageTemplateModule.Header;
pageModule.Footer = pageTemplateModule.Footer;
pageModule.IsDeleted = pageTemplateModule.IsDeleted;
pageModule.Module.PermissionList = new List<Permission>();
foreach (var permission in pageTemplateModule.PermissionList)

View File

@ -285,6 +285,8 @@ namespace Oqtane.Services
ContainerType = pagemodule.ContainerType,
EffectiveDate = pagemodule.EffectiveDate,
ExpiryDate = pagemodule.ExpiryDate,
Header = pagemodule.Header,
Footer = pagemodule.Footer,
ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == pagemodule.Module.ModuleDefinitionName)),

View File

@ -113,6 +113,18 @@ namespace Oqtane.Models
[NotMapped]
public DateTime? ExpiryDate { get; set; }
/// <summary>
/// Header content to include at the top of a module instance in the UI
/// </summary>
[NotMapped]
public string Header { get; set; }
/// <summary>
/// Footer content to include below a module instance in the UI
/// </summary>
[NotMapped]
public string Footer { get; set; }
#endregion
#region SiteRouter properties
@ -218,6 +230,8 @@ namespace Oqtane.Models
ContainerType = ContainerType,
EffectiveDate = EffectiveDate,
ExpiryDate = ExpiryDate,
Header = Header,
Footer = Footer,
CreatedBy = CreatedBy,
CreatedOn = CreatedOn,
ModifiedBy = ModifiedBy,

View File

@ -41,14 +41,27 @@ namespace Oqtane.Models
/// Reference to a Razor Container which wraps this module instance.
/// </summary>
public string ContainerType { get; set; }
/// <summary>
/// Start of when this assignment is valid. See also <see cref="ExpiryDate"/>
/// </summary>
public DateTime? EffectiveDate { get; set; }
/// <summary>
/// End of when this assignment is valid. See also <see cref="EffectiveDate"/>
/// </summary>
public DateTime? ExpiryDate { get; set; }
/// <summary>
/// Header content to include above the module instance in the UI
/// </summary>
public string Header { get; set; }
/// <summary>
/// Footer content to include below the module instance in the UI
/// </summary>
public string Footer { get; set; }
#region IDeletable Properties
public string DeletedBy { get; set; }

View File

@ -95,6 +95,8 @@ namespace Oqtane.Models
Pane = PaneNames.Default;
Order = 1;
ContainerType = "";
Header = "";
Footer = "";
IsDeleted = false;
PermissionList = new List<Permission>()
{
@ -110,6 +112,8 @@ namespace Oqtane.Models
public string Pane { get; set; }
public int Order { get; set; }
public string ContainerType { get; set; }
public string Header { get; set; }
public string Footer { get; set; }
public bool IsDeleted { get; set; }
public List<Permission> PermissionList { get; set; }
public List<Setting> Settings { get; set; }