feat: working on making handlebars a little less painful
This commit is contained in:
parent
858080f5fb
commit
88dc0e6fe7
11 changed files with 249 additions and 74 deletions
|
@ -15,18 +15,18 @@ namespace Nitride.Handlebars;
|
|||
/// An operation that applies a common or shared template on the content of
|
||||
/// a document that includes theme or styling information.
|
||||
/// </summary>
|
||||
public class ApplyStyleTemplate<TModel> : OperationBase
|
||||
[WithProperties]
|
||||
public partial class ApplyStyleTemplate : OperationBase
|
||||
{
|
||||
// TODO: This does not use [WithProperties] because the source generator hasn't been taught how to do generics.
|
||||
|
||||
private readonly HandlebarsTemplateCache cache;
|
||||
|
||||
private readonly IValidator<ApplyStyleTemplate<TModel>> validator;
|
||||
private readonly IValidator<ApplyStyleTemplate> validator;
|
||||
|
||||
public ApplyStyleTemplate(HandlebarsTemplateCache cache)
|
||||
public ApplyStyleTemplate(
|
||||
IValidator<ApplyStyleTemplate> validator,
|
||||
HandlebarsTemplateCache cache)
|
||||
{
|
||||
// TODO: Figure out why Autofac won't let us register IValidator of generic classes.
|
||||
this.validator = new ApplyStyleTemplateValidator<TModel>();
|
||||
this.validator = validator;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class ApplyStyleTemplate<TModel> : OperationBase
|
|||
/// entity. This allows for the website to customize what information is
|
||||
/// being passed to the template.
|
||||
/// </summary>
|
||||
public Func<Entity, TModel>? CreateModelCallback { get; set; }
|
||||
public Func<Entity, object>? CreateModelCallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback used to determine which template to use
|
||||
|
@ -55,38 +55,11 @@ public class ApplyStyleTemplate<TModel> : OperationBase
|
|||
return input.SelectEntity<ITextContent>(this.Apply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the callback for the template and returns the operation to
|
||||
/// chain operations.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to set.</param>
|
||||
/// <returns>The ApplyContentHandlebarsTemplate to chain requests.</returns>
|
||||
public ApplyStyleTemplate<TModel> WithCreateModelCallback(Func<Entity, TModel>? callback)
|
||||
{
|
||||
this.CreateModelCallback = callback;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ApplyStyleTemplate<TModel> WithGetTemplateName(Func<Entity, string>? callback)
|
||||
{
|
||||
this.GetTemplateName = callback;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ApplyStyleTemplate<TModel> WithHandlebars(IHandlebars? handlebars)
|
||||
{
|
||||
this.Handlebars = handlebars;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Entity Apply(
|
||||
Entity entity,
|
||||
ITextContent content)
|
||||
{
|
||||
TModel model = this.CreateModelCallback!(entity);
|
||||
object model = this.CreateModelCallback!(entity);
|
||||
string name = this.GetTemplateName!(entity);
|
||||
HandlebarsTemplate<object, object> template = this.cache.GetNamedTemplate(name);
|
||||
string result = template(model!);
|
||||
|
|
|
@ -2,7 +2,7 @@ using FluentValidation;
|
|||
|
||||
namespace Nitride.Handlebars;
|
||||
|
||||
public class ApplyStyleTemplateValidator<TModel> : AbstractValidator<ApplyStyleTemplate<TModel>>
|
||||
public class ApplyStyleTemplateValidator : AbstractValidator<ApplyStyleTemplate>
|
||||
{
|
||||
public ApplyStyleTemplateValidator()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
using System.IO;
|
||||
|
||||
using HandlebarsDotNet;
|
||||
|
||||
namespace Nitride.Handlebars.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the templates from the given directory.
|
||||
/// </summary>
|
||||
public class FileSystemHandlebarsTemplateLoader : IHandlebarsLoader
|
||||
{
|
||||
private readonly DirectoryInfo directory;
|
||||
|
||||
private readonly string pattern;
|
||||
|
||||
public FileSystemHandlebarsTemplateLoader(
|
||||
DirectoryInfo directory,
|
||||
string pattern = "*.hbs")
|
||||
{
|
||||
this.directory = directory;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Register(IHandlebars handlebars)
|
||||
{
|
||||
foreach (FileInfo file in this.directory.GetFiles(this.pattern))
|
||||
{
|
||||
string name = Path.GetFileNameWithoutExtension(file.Name);
|
||||
string content = File.ReadAllText(file.FullName);
|
||||
|
||||
handlebars.RegisterTemplate(name, content);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using HandlebarsDotNet;
|
||||
|
||||
namespace Nitride.Handlebars.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">A typesafe model of the template.</typeparam>
|
||||
public class ForEachHandlebarsBlock<TModel> : HandlebarsBlockBase
|
||||
{
|
||||
public ForEachHandlebarsBlock(string helperName)
|
||||
{
|
||||
this.HelperName = helperName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list that needs to be rendered.
|
||||
/// </summary>
|
||||
public Func<TModel, IEnumerable<object>>? GetList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback that is called when nothing is found.
|
||||
/// </summary>
|
||||
public Action<EncodedTextWriter, BlockHelperOptions, Context, Arguments>? NothingFound { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string HelperName { get; }
|
||||
|
||||
public ForEachHandlebarsBlock<TModel> WithGetList(Func<TModel, IEnumerable<object>>? callback)
|
||||
{
|
||||
this.GetList = callback;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ForEachHandlebarsBlock<TModel> WithNothingFoundText(
|
||||
Action<EncodedTextWriter, BlockHelperOptions, Context, Arguments>? callback)
|
||||
{
|
||||
this.NothingFound = callback;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the output to write out text when no items are found.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to write out.</param>
|
||||
/// <returns>The same object for chained methods.</returns>
|
||||
public ForEachHandlebarsBlock<TModel> WithNothingFoundText(string text)
|
||||
{
|
||||
this.NothingFound = (
|
||||
output,
|
||||
_1,
|
||||
_2,
|
||||
_3) => output.Write(text);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Render(
|
||||
EncodedTextWriter output,
|
||||
BlockHelperOptions options,
|
||||
Context context,
|
||||
Arguments input)
|
||||
{
|
||||
if (context.Value is not TModel model)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format(
|
||||
"Cannot apply the {0} on context value because it is {1}.",
|
||||
nameof(ForEachHandlebarsBlock<TModel>),
|
||||
context.Value.GetType()));
|
||||
}
|
||||
|
||||
IEnumerable<object>? list = this.GetList?.Invoke(model);
|
||||
bool hasItems = false;
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
foreach (object item in list)
|
||||
{
|
||||
hasItems = true;
|
||||
options.Template(output, item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasItems)
|
||||
{
|
||||
this.NothingFound?.Invoke(output, options, context, input);
|
||||
}
|
||||
}
|
||||
}
|
30
src/Nitride.Handlebars/Configuration/HandlebarsBlockBase.cs
Normal file
30
src/Nitride.Handlebars/Configuration/HandlebarsBlockBase.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using HandlebarsDotNet;
|
||||
|
||||
namespace Nitride.Handlebars.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a block helper which can be registered.
|
||||
/// </summary>
|
||||
public abstract class HandlebarsBlockBase : IHandlebarsLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the helper, which is how it is called in the template.
|
||||
/// </summary>
|
||||
protected abstract string HelperName { get; }
|
||||
|
||||
public void Register(IHandlebars handlebars)
|
||||
{
|
||||
HandlebarsBlockHelper blockHelper = this.Render;
|
||||
|
||||
handlebars.RegisterHelper(this.HelperName, blockHelper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the helper to the template.
|
||||
/// </summary>
|
||||
protected abstract void Render(
|
||||
EncodedTextWriter output,
|
||||
BlockHelperOptions options,
|
||||
Context context,
|
||||
Arguments input);
|
||||
}
|
15
src/Nitride.Handlebars/Configuration/IHandlebarsLoader.cs
Normal file
15
src/Nitride.Handlebars/Configuration/IHandlebarsLoader.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using HandlebarsDotNet;
|
||||
|
||||
namespace Nitride.Handlebars.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a dependency injected loader of templates or modules.
|
||||
/// </summary>
|
||||
public interface IHandlebarsLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the given helper into the handlebars engine.
|
||||
/// </summary>
|
||||
/// <param name="handlebars">The handlebars to register the helper into.</param>
|
||||
void Register(IHandlebars handlebars);
|
||||
}
|
|
@ -1,11 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Autofac;
|
||||
|
||||
using Nitride.Handlebars.Configuration;
|
||||
|
||||
namespace Nitride.Handlebars;
|
||||
|
||||
public static class NitrideHandlebarsBuilderExtensions
|
||||
{
|
||||
public static NitrideBuilder UseHandlebars(this NitrideBuilder builder)
|
||||
{
|
||||
return builder.ConfigureContainer(x => x.RegisterModule<NitrideHandlebarsModule>());
|
||||
return builder
|
||||
.ConfigureContainer(x => x.RegisterModule<NitrideHandlebarsModule>());
|
||||
}
|
||||
|
||||
public static NitrideBuilder UseHandlebars(
|
||||
this NitrideBuilder builder,
|
||||
Func<ContainerBuilder, IEnumerable<IHandlebarsLoader>> configure)
|
||||
{
|
||||
builder.UseHandlebars();
|
||||
|
||||
builder.ConfigureContainer(
|
||||
c =>
|
||||
{
|
||||
IEnumerable<IHandlebarsLoader> loaders = configure(c);
|
||||
|
||||
foreach (IHandlebarsLoader loader in loaders)
|
||||
{
|
||||
c.RegisterInstance(loader)
|
||||
.As<IHandlebarsLoader>()
|
||||
.SingleInstance();
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Autofac;
|
||||
|
||||
using HandlebarsDotNet;
|
||||
|
||||
using Nitride.Handlebars.Configuration;
|
||||
|
||||
namespace Nitride.Handlebars;
|
||||
|
||||
public class NitrideHandlebarsModule : Module
|
||||
|
@ -14,10 +20,20 @@ public class NitrideHandlebarsModule : Module
|
|||
.AsSelf()
|
||||
.SingleInstance();
|
||||
|
||||
builder.RegisterGeneric(typeof(RenderContentTemplate<>))
|
||||
.As(typeof(RenderContentTemplate<>));
|
||||
builder.Register(
|
||||
(context) =>
|
||||
{
|
||||
IHandlebars handlebars = HandlebarsDotNet.Handlebars.Create();
|
||||
IEnumerable<IHandlebarsLoader> helpers = context.Resolve<IEnumerable<IHandlebarsLoader>>();
|
||||
|
||||
builder.RegisterGeneric(typeof(ApplyStyleTemplate<>))
|
||||
.As(typeof(ApplyStyleTemplate<>));
|
||||
foreach (IHandlebarsLoader helper in helpers)
|
||||
{
|
||||
helper.Register(handlebars);
|
||||
}
|
||||
|
||||
return handlebars;
|
||||
})
|
||||
.As<IHandlebars>()
|
||||
.SingleInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,18 +16,18 @@ namespace Nitride.Handlebars;
|
|||
/// applied against the content and metadata and then replaces the content
|
||||
/// of that entity.
|
||||
/// </summary>
|
||||
public class RenderContentTemplate<TModel> : OperationBase
|
||||
[WithProperties]
|
||||
public partial class RenderContentTemplate : OperationBase
|
||||
{
|
||||
private readonly HandlebarsTemplateCache cache;
|
||||
|
||||
// TODO: This does not use [WithProperties] because the source generator hasn't been taught how to do generics.
|
||||
private readonly IValidator<RenderContentTemplate> validator;
|
||||
|
||||
private readonly IValidator<RenderContentTemplate<TModel>> validator;
|
||||
|
||||
public RenderContentTemplate(HandlebarsTemplateCache cache)
|
||||
public RenderContentTemplate(
|
||||
IValidator<RenderContentTemplate> validator,
|
||||
HandlebarsTemplateCache cache)
|
||||
{
|
||||
// TODO: Figure out why Autofac won't let us register IValidator of generic classes.
|
||||
this.validator = new RenderContentTemplateValidator<TModel>();
|
||||
this.validator = validator;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ public class RenderContentTemplate<TModel> : OperationBase
|
|||
/// entity. This allows for the website to customize what information is
|
||||
/// being passed to the template.
|
||||
/// </summary>
|
||||
public Func<Entity, TModel>? CreateModelCallback { get; set; }
|
||||
public Func<Entity, object>? CreateModelCallback { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
|
||||
|
@ -46,36 +46,18 @@ public class RenderContentTemplate<TModel> : OperationBase
|
|||
return input.SelectEntity<HasHandlebarsTemplate, ITextContent>(this.Apply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the callback for the template and returns the operation to
|
||||
/// chain operations.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback to set.</param>
|
||||
/// <returns>The ApplyContentHandlebarsTemplate to chain requests.</returns>
|
||||
public RenderContentTemplate<TModel> WithCreateModelCallback(Func<Entity, TModel>? callback)
|
||||
{
|
||||
this.CreateModelCallback = callback;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Entity Apply(
|
||||
Entity entity,
|
||||
HasHandlebarsTemplate _,
|
||||
ITextContent content)
|
||||
{
|
||||
// Create the model using the callback.
|
||||
TModel model = this.CreateModelCallback!(entity);
|
||||
|
||||
// Create a template from the contents.
|
||||
string text = content.GetText();
|
||||
HandlebarsTemplate<object, object> template = this.cache.GetLiteralTemplate(text);
|
||||
|
||||
// Render the template and create a new entity with the updated
|
||||
// text.
|
||||
object model = this.CreateModelCallback!(entity);
|
||||
string result = template(model!);
|
||||
|
||||
return entity.Remove<HasHandlebarsTemplate>()
|
||||
.SetTextContent(new StringTextContent(result));
|
||||
return entity
|
||||
.Remove<HasHandlebarsTemplate>()
|
||||
.SetTextContent(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ using FluentValidation;
|
|||
|
||||
namespace Nitride.Handlebars;
|
||||
|
||||
public class RenderContentTemplateValidator<TModel> : AbstractValidator<RenderContentTemplate<TModel>>
|
||||
public class RenderContentTemplateValidator : AbstractValidator<RenderContentTemplate>
|
||||
{
|
||||
public RenderContentTemplateValidator()
|
||||
{
|
||||
|
|
|
@ -92,7 +92,8 @@ public class NitrideBuilder
|
|||
"Application name must be set, such as with the NitrideBuilder.WithApplicationName() to properly run.");
|
||||
}
|
||||
|
||||
return await ToolBoxBuilder.Create(this.nitrideModule.ApplicationName, this.arguments)
|
||||
return await ToolBoxBuilder
|
||||
.Create(this.nitrideModule.ApplicationName, this.arguments)
|
||||
.ConfigureContainer(this.ConfigureContainer)
|
||||
.Build()
|
||||
.RunAsync();
|
||||
|
|
Reference in a new issue