313 lines
12 KiB
C#
313 lines
12 KiB
C#
// /**
|
|
// * File: TemplateElement.cs
|
|
// * Author: haraldwolff
|
|
// *
|
|
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
|
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
|
// *
|
|
// *
|
|
// **/
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Jint.Native;
|
|
using Jint.Native.Object;
|
|
using ln.templates.script;
|
|
|
|
namespace ln.templates.html
|
|
{
|
|
public partial class TemplateElement : Element
|
|
{
|
|
private static string[] hiddenTags = new[] { "template", "slot" };
|
|
|
|
Dictionary<string, NewExpression> expressions = new Dictionary<string, NewExpression>();
|
|
Dictionary<string, NewExpression> loops = new Dictionary<string, NewExpression>();
|
|
Dictionary<string, NewExpression> _vsets = new Dictionary<string, NewExpression>();
|
|
List<NewExpression> conditions = new List<NewExpression>();
|
|
|
|
private Dictionary<string, Element> _slots = new Dictionary<string, Element>();
|
|
|
|
private NewExpression classExpression;
|
|
|
|
bool hideTag;
|
|
|
|
public TemplateElement(string tagName)
|
|
:base(tagName)
|
|
{
|
|
hideTag = hiddenTags.Contains(tagName.ToLower());
|
|
}
|
|
|
|
public override void SetAttribute(string attributeName, string attributeValue)
|
|
{
|
|
if (attributeName.StartsWith("v-for:", StringComparison.InvariantCultureIgnoreCase))
|
|
loops.Add(attributeName.Substring(6), new NewExpression(attributeValue));
|
|
else if (attributeName.Equals("v-if", StringComparison.InvariantCultureIgnoreCase))
|
|
conditions.Add(new NewExpression(attributeValue));
|
|
else if (attributeName.StartsWith("v-set:", StringComparison.InvariantCultureIgnoreCase))
|
|
_vsets.Add(attributeName.Substring(6), new NewExpression(attributeValue));
|
|
else if (attributeName.Equals(":class", StringComparison.InvariantCultureIgnoreCase))
|
|
classExpression = new NewExpression(attributeValue);
|
|
else if (attributeName.StartsWith("::"))
|
|
base.SetAttribute(attributeName, attributeValue);
|
|
else if (attributeName[0] == ':')
|
|
expressions.Add(attributeName.Substring(1), new NewExpression(attributeValue));
|
|
else
|
|
base.SetAttribute(attributeName, attributeValue);
|
|
}
|
|
|
|
public void SetSlot(string slotName, Element slotElement)
|
|
{
|
|
_slots.Add(slotName, slotElement);
|
|
}
|
|
|
|
/**
|
|
* This is the render method to be called from external callers
|
|
*/
|
|
public virtual void RenderTemplate(Context renderContext)
|
|
{
|
|
if (loops.Count == 0)
|
|
{
|
|
RenderElement(renderContext);
|
|
}
|
|
else
|
|
{
|
|
Stack<KeyValuePair<string, NewExpression>> loopStack =
|
|
new Stack<KeyValuePair<string, NewExpression>>(loops);
|
|
DoLoop(renderContext, loopStack);
|
|
}
|
|
}
|
|
|
|
private Template TemplateFromExpression(Context renderContext, NewExpression expression)
|
|
{
|
|
object templateValue = expression.Resolve(renderContext.Engine);
|
|
if (!(templateValue is Template template))
|
|
{
|
|
template = renderContext.Resolver.GetTemplateByPath((string)templateValue);
|
|
}
|
|
|
|
return template;
|
|
}
|
|
|
|
protected virtual void DoLoop(Context renderContext, Stack<KeyValuePair<string, NewExpression>> loopStack)
|
|
{
|
|
if (loopStack.Count == 0)
|
|
{
|
|
RenderElement(renderContext);
|
|
} else
|
|
{
|
|
KeyValuePair<string, NewExpression> loop = loopStack.Pop();
|
|
IEnumerable enumerable = (IEnumerable)loop.Value.Resolve(renderContext.Engine);
|
|
if (enumerable != null)
|
|
{
|
|
bool hadBinding = renderContext.Engine.Realm.GlobalEnv.HasBinding(loop.Key);
|
|
JsValue _save = null;
|
|
if (hadBinding)
|
|
_save = renderContext.Engine.Realm.GlobalEnv.GetBindingValue(loop.Key, false);
|
|
try
|
|
{
|
|
|
|
foreach (object o in enumerable)
|
|
{
|
|
renderContext.Engine.SetValue(loop.Key, o);
|
|
DoLoop(renderContext, loopStack);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (hadBinding)
|
|
renderContext.Engine.Realm.GlobalEnv.SetMutableBinding(loop.Key, _save, false);
|
|
else
|
|
renderContext.Engine.Realm.GlobalEnv.DeleteBinding(loop.Key);
|
|
}
|
|
}
|
|
loopStack.Push(loop);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is the render method to be called to render the element itself (tag, attributes, children, ... )
|
|
*/
|
|
public virtual void RenderElement(Context renderContext)
|
|
{
|
|
Dictionary<string, object> savesets = null;
|
|
|
|
if (checkConditions(renderContext))
|
|
{
|
|
if (_vsets.Count > 0)
|
|
{
|
|
savesets = new Dictionary<string, object>();
|
|
foreach (var vset in _vsets)
|
|
{
|
|
if (renderContext.Engine.Realm.GlobalEnv.HasBinding(vset.Key))
|
|
savesets.Add(vset.Key, renderContext.Engine.Realm.GlobalEnv.GetBindingValue(vset.Key, false));
|
|
renderContext.Engine.SetValue(vset.Key, vset.Value.Resolve(renderContext.Engine));
|
|
}
|
|
}
|
|
|
|
if (TryGetAttribute(renderContext, "v-include", out string templatePath))
|
|
{
|
|
Template template = renderContext.Resolver.GetTemplateByPath(templatePath);
|
|
Context context = new Context(renderContext);
|
|
if (this._slots.Count == 0)
|
|
context.SetSlot("", this);
|
|
else
|
|
context.SetSlots(this._slots);
|
|
|
|
template.Render(context);
|
|
}
|
|
else
|
|
{
|
|
if (!hideTag)
|
|
{
|
|
renderContext.TargetWriter.Write("<{0}", Name);
|
|
|
|
object classObject = null;
|
|
if (classExpression is not null)
|
|
{
|
|
classObject = renderContext.Engine.Evaluate("(function(){ return " + classExpression
|
|
.ExpressionText + ";})()");
|
|
|
|
HashSet<string> classes = new HashSet<string>(GetAttribute("class", "").Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
|
var classObjectInstance = classObject as ObjectInstance;
|
|
|
|
foreach (var property in classObjectInstance.GetOwnProperties())
|
|
{
|
|
if ((property.Value.Value is JsBoolean jsBooleanValue) &&
|
|
jsBooleanValue.Equals(JsBoolean.True))
|
|
classes.Add(property.Key.ToString());
|
|
}
|
|
|
|
renderContext.TargetWriter.Write(" class=\"{0}\"", string.Join(" ", classes));
|
|
}
|
|
|
|
foreach (KeyValuePair<String, String> attributePair in Attributes)
|
|
{
|
|
if (!attributePair.Key.Equals("class") || (classExpression is null))
|
|
{
|
|
renderContext.TargetWriter.Write(" {0}=\"{1}\"", attributePair.Key,
|
|
attributePair.Value);
|
|
}
|
|
}
|
|
|
|
foreach (KeyValuePair<string,NewExpression> keyValuePair in expressions)
|
|
{
|
|
object value = keyValuePair.Value.Resolve(renderContext.Engine);
|
|
if (value != null)
|
|
{
|
|
renderContext.TargetWriter.Write(" {0}=\"{1}\"", keyValuePair.Key, value);
|
|
}
|
|
}
|
|
|
|
renderContext.TargetWriter.Write(">");
|
|
}
|
|
|
|
if (!IsVoid)
|
|
{
|
|
RenderElements(renderContext, Children);
|
|
|
|
if (!hideTag)
|
|
renderContext.TargetWriter.Write("</{0}>", Name);
|
|
}
|
|
}
|
|
|
|
if (savesets?.Count > 0)
|
|
{
|
|
foreach (var saveset in savesets)
|
|
renderContext.Engine.SetValue(saveset.Key, saveset.Value);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public string GetAttribute(Context context, string attributeName)
|
|
{
|
|
if (expressions.TryGetValue(attributeName, out NewExpression expression))
|
|
return expression.Resolve(context.Engine).ToString();
|
|
return GetAttribute(attributeName);
|
|
}
|
|
public bool TryGetAttribute(Context context, string attributeName, out string attributeValue)
|
|
{
|
|
if (expressions.TryGetValue(attributeName, out NewExpression expression))
|
|
{
|
|
attributeValue = expression.Resolve(context.Engine).ToString();
|
|
return true;
|
|
}
|
|
else if (HasAttribute(attributeName))
|
|
{
|
|
attributeValue = GetAttribute(attributeName);
|
|
return true;
|
|
}
|
|
|
|
attributeValue = null;
|
|
return false;
|
|
}
|
|
|
|
protected void RenderElements(Context renderContext, IEnumerable<Element> elements)
|
|
{
|
|
foreach (Element element in elements)
|
|
{
|
|
if (element is TemplateElement templateElement)
|
|
{
|
|
templateElement.RenderTemplate(renderContext);
|
|
}
|
|
else
|
|
{
|
|
renderContext.TargetWriter.Write(element.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool checkConditions(Context renderContext)
|
|
{
|
|
foreach (NewExpression condition in conditions)
|
|
if (!condition.IsTrue(renderContext.Engine))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
class TemplateScriptElement : TemplateElement
|
|
{
|
|
private StringBuilder scriptBuilder = new StringBuilder();
|
|
|
|
public TemplateScriptElement()
|
|
: base("template-script")
|
|
{
|
|
}
|
|
|
|
public override void AppendChild(Element element)
|
|
{
|
|
if (element is TextElement textElement)
|
|
{
|
|
scriptBuilder.Append(textElement.Text);
|
|
}
|
|
else
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
|
|
public override void RenderElement(Context renderContext) =>
|
|
renderContext.Engine.Execute(scriptBuilder.ToString());
|
|
|
|
}
|
|
|
|
public static TemplateElement Create(string tagName)
|
|
{
|
|
switch (tagName)
|
|
{
|
|
case "qrcode":
|
|
return new QrCodeElement(tagName);
|
|
case "slot":
|
|
return new SlotElement(tagName);
|
|
case "template-script":
|
|
return new TemplateScriptElement();
|
|
default:
|
|
return new TemplateElement(tagName);
|
|
}
|
|
}
|
|
}
|
|
}
|