htmgo/framework/h/renderer.go

276 lines
5.7 KiB
Go
Raw Permalink Normal View History

package h
import (
"fmt"
2024-09-21 03:59:07 +00:00
"github.com/maddalax/htmgo/framework/hx"
2024-09-29 06:05:22 +00:00
"html"
"html/template"
"strings"
)
2024-09-28 02:29:53 +00:00
type CustomElement = string
var (
2024-09-28 16:27:07 +00:00
CachedNodeTag CustomElement = "htmgo_cache_node"
CachedNodeByKeyEntry CustomElement = "htmgo_cached_node_by_key_entry"
2024-09-28 02:29:53 +00:00
)
2024-09-27 21:18:09 +00:00
/*
*
void tags are tags that cannot have children
*/
var voidTags = map[string]bool{
"area": true,
"base": true,
"br": true,
"col": true,
"embed": true,
"hr": true,
"img": true,
"input": true,
"link": true,
"meta": true,
"source": true,
"track": true,
"wbr": true,
}
2024-10-29 12:08:44 +00:00
type ScriptEntry struct {
Body string
ChildOf *Element
}
2024-09-22 15:46:38 +00:00
type RenderContext struct {
2024-10-29 12:08:44 +00:00
builder *strings.Builder
scripts []ScriptEntry
currentElement *Element
2024-09-22 15:46:38 +00:00
}
func (ctx *RenderContext) AddScript(funcName string, body string) {
script := fmt.Sprintf(`
<script id="%s">
2024-10-01 17:09:22 +00:00
function %s(self, event) {
let e = event;
2024-09-22 15:46:38 +00:00
%s
}
</script>`, funcName, funcName, body)
2024-10-29 12:08:44 +00:00
ctx.scripts = append(ctx.scripts, ScriptEntry{
Body: script,
ChildOf: ctx.currentElement,
})
2024-09-22 15:46:38 +00:00
}
func (node *Element) Render(context *RenderContext) {
2024-09-30 22:31:09 +00:00
if node == nil {
return
}
2024-09-20 02:13:08 +00:00
2024-10-29 12:08:44 +00:00
context.currentElement = node
2024-09-28 02:29:53 +00:00
if node.tag == CachedNodeTag {
meta := node.meta.(*CachedNode)
meta.Render(context)
2024-09-28 16:27:07 +00:00
return
}
if node.tag == CachedNodeByKeyEntry {
meta := node.meta.(*ByKeyEntry)
meta.Render(context)
2024-09-28 02:29:53 +00:00
return
}
2024-09-30 22:31:09 +00:00
// some elements may not have a tag, such as a Fragment
if node.tag != "" {
2024-09-27 14:46:45 +00:00
context.builder.WriteString("<")
context.builder.WriteString(node.tag)
2024-09-30 17:47:10 +00:00
node.attributes.Each(func(key string, value string) {
2024-09-30 17:39:48 +00:00
NewAttribute(key, value).Render(context)
})
}
2024-09-27 15:10:00 +00:00
totalChildren := 0
shouldFlatten := false
for _, child := range node.children {
2024-09-27 15:10:00 +00:00
switch c := child.(type) {
case *ChildList:
2024-09-27 15:10:00 +00:00
shouldFlatten = true
totalChildren += len(c.Children)
default:
2024-09-27 15:10:00 +00:00
totalChildren++
}
}
2024-09-27 15:10:00 +00:00
if shouldFlatten {
// first pass, flatten the children
flatChildren := make([]Ren, totalChildren)
2024-09-29 14:45:17 +00:00
index := 0
for _, child := range node.children {
2024-09-27 15:10:00 +00:00
switch c := child.(type) {
case *ChildList:
for _, ren := range c.Children {
2024-09-29 14:45:17 +00:00
flatChildren[index] = ren
index++
2024-09-27 15:10:00 +00:00
}
default:
2024-09-29 14:45:17 +00:00
flatChildren[index] = child
index++
2024-09-27 15:10:00 +00:00
}
}
node.children = flatChildren
}
// second pass, render any attributes within the tag
for _, child := range node.children {
switch child.(type) {
2024-09-30 17:39:48 +00:00
case *AttributeMapOrdered:
2024-09-22 15:46:38 +00:00
child.Render(context)
2024-09-27 15:10:00 +00:00
case *AttributeR:
child.Render(context)
case *LifeCycle:
2024-09-22 15:46:38 +00:00
child.Render(context)
}
}
// close the tag
if node.tag != "" {
2024-09-27 21:18:09 +00:00
if voidTags[node.tag] {
context.builder.WriteString("/")
}
2024-09-22 15:46:38 +00:00
context.builder.WriteString(">")
}
2024-09-27 21:18:09 +00:00
// void elements do not have children
if !voidTags[node.tag] {
// render the children elements that are not attributes
for _, child := range node.children {
switch child.(type) {
2024-09-30 17:39:48 +00:00
case *AttributeMapOrdered:
2024-09-27 21:18:09 +00:00
continue
case *AttributeR:
continue
case *LifeCycle:
continue
default:
child.Render(context)
}
}
}
if node.tag != "" {
2024-10-29 12:08:44 +00:00
renderScripts(context, node)
2024-09-27 21:18:09 +00:00
if !voidTags[node.tag] {
context.builder.WriteString("</")
context.builder.WriteString(node.tag)
context.builder.WriteString(">")
}
2024-09-22 15:46:38 +00:00
}
}
2024-10-29 12:08:44 +00:00
func renderScripts(context *RenderContext, parent *Element) {
if len(context.scripts) == 0 {
return
}
notWritten := make([]ScriptEntry, 0)
2024-09-22 15:46:38 +00:00
for _, script := range context.scripts {
2024-10-29 12:08:44 +00:00
if script.ChildOf == parent {
context.builder.WriteString(script.Body)
} else {
notWritten = append(notWritten, script)
}
}
2024-10-29 12:08:44 +00:00
context.scripts = notWritten
}
2024-09-22 15:46:38 +00:00
func (a *AttributeR) Render(context *RenderContext) {
2024-09-30 17:39:48 +00:00
context.builder.WriteString(" ")
2024-09-27 14:46:45 +00:00
context.builder.WriteString(a.Name)
2024-09-29 06:05:22 +00:00
if a.Value != "" {
context.builder.WriteString(`=`)
context.builder.WriteString(`"`)
context.builder.WriteString(html.EscapeString(a.Value))
context.builder.WriteString(`"`)
}
}
2024-09-22 15:46:38 +00:00
func (t *TextContent) Render(context *RenderContext) {
context.builder.WriteString(template.HTMLEscapeString(t.Content))
}
2024-09-22 15:46:38 +00:00
func (r *RawContent) Render(context *RenderContext) {
context.builder.WriteString(r.Content)
}
2024-09-22 15:46:38 +00:00
func (c *ChildList) Render(context *RenderContext) {
for _, child := range c.Children {
2024-09-22 15:46:38 +00:00
child.Render(context)
}
}
2024-09-22 15:46:38 +00:00
func (j SimpleJsCommand) Render(context *RenderContext) {
context.builder.WriteString(j.Command)
}
func (j ComplexJsCommand) Render(context *RenderContext) {
context.builder.WriteString(j.Command)
}
func (p *Partial) Render(context *RenderContext) {
p.Root.Render(context)
}
2024-09-30 17:39:48 +00:00
func (m *AttributeMapOrdered) Render(context *RenderContext) {
2024-09-30 17:47:10 +00:00
m.Each(func(key string, value string) {
2024-09-30 17:39:48 +00:00
NewAttribute(key, value).Render(context)
})
}
2024-09-22 15:46:38 +00:00
func (l *LifeCycle) fromAttributeMap(event string, key string, value string, context *RenderContext) {
2024-09-21 03:59:07 +00:00
if key == hx.GetAttr || key == hx.PatchAttr || key == hx.PostAttr {
2024-09-23 02:33:22 +00:00
HxTriggerString(hx.ToHtmxTriggerName(event)).Render(context)
2024-09-21 03:59:07 +00:00
}
2024-09-22 15:46:38 +00:00
Attribute(key, value).Render(context)
2024-09-21 03:59:07 +00:00
}
2024-09-22 15:46:38 +00:00
func (l *LifeCycle) Render(context *RenderContext) {
m := make(map[string]string)
for event, commands := range l.handlers {
m[event] = ""
2024-09-30 17:47:10 +00:00
for _, command := range commands {
2024-09-21 03:59:07 +00:00
switch c := command.(type) {
2024-09-22 15:46:38 +00:00
case SimpleJsCommand:
2024-10-01 17:09:22 +00:00
m[event] += fmt.Sprintf("var self=this;var e=event;%s;", c.Command)
2024-09-22 15:46:38 +00:00
case ComplexJsCommand:
context.AddScript(c.TempFuncName, c.Command)
2024-10-01 17:09:22 +00:00
m[event] += fmt.Sprintf("%s(this, event);", c.TempFuncName)
2024-09-30 17:39:48 +00:00
case *AttributeMapOrdered:
2024-09-30 17:47:10 +00:00
c.Each(func(key string, value string) {
l.fromAttributeMap(event, key, value, context)
2024-09-30 17:39:48 +00:00
})
2024-09-27 15:10:00 +00:00
case *AttributeR:
l.fromAttributeMap(event, c.Name, c.Value, context)
2024-09-21 03:59:07 +00:00
}
2024-09-30 17:47:10 +00:00
}
}
children := make([]Ren, 0)
2024-09-21 03:59:07 +00:00
for event, value := range m {
if value != "" {
children = append(children, Attribute(event, value))
}
}
if len(children) == 0 {
return
}
2024-09-22 15:46:38 +00:00
Children(children...).Render(context)
}