2024-09-20 01:24:44 +00:00
|
|
|
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"
|
2024-09-29 01:45:27 +00:00
|
|
|
"html/template"
|
2024-09-20 01:24:44 +00:00
|
|
|
"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-09-22 15:46:38 +00:00
|
|
|
type RenderContext struct {
|
|
|
|
|
builder *strings.Builder
|
|
|
|
|
scripts []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ctx *RenderContext) AddScript(funcName string, body string) {
|
|
|
|
|
script := fmt.Sprintf(`
|
|
|
|
|
<script id="%s">
|
2024-09-25 21:57:13 +00:00
|
|
|
function %s(self) {
|
2024-09-22 15:46:38 +00:00
|
|
|
%s
|
|
|
|
|
}
|
|
|
|
|
</script>`, funcName, funcName, body)
|
|
|
|
|
ctx.scripts = append(ctx.scripts, script)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (node *Element) Render(context *RenderContext) {
|
2024-09-20 01:24:44 +00:00
|
|
|
// some elements may not have a tag, such as a Fragment
|
2024-09-20 02:13:08 +00:00
|
|
|
|
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-20 01:24:44 +00:00
|
|
|
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-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-27 15:10:00 +00:00
|
|
|
totalChildren := 0
|
|
|
|
|
shouldFlatten := false
|
2024-09-20 01:24:44 +00:00
|
|
|
for _, child := range node.children {
|
2024-09-27 15:10:00 +00:00
|
|
|
switch c := child.(type) {
|
2024-09-20 01:24:44 +00:00
|
|
|
case *ChildList:
|
2024-09-27 15:10:00 +00:00
|
|
|
shouldFlatten = true
|
|
|
|
|
totalChildren += len(c.Children)
|
2024-09-20 01:24:44 +00:00
|
|
|
default:
|
2024-09-27 15:10:00 +00:00
|
|
|
totalChildren++
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2024-09-20 01:24:44 +00:00
|
|
|
|
|
|
|
|
// 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)
|
2024-09-20 01:24:44 +00:00
|
|
|
case *LifeCycle:
|
2024-09-22 15:46:38 +00:00
|
|
|
child.Render(context)
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if node.tag != "" {
|
2024-09-22 15:46:38 +00:00
|
|
|
renderScripts(context)
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func renderScripts(context *RenderContext) {
|
|
|
|
|
for _, script := range context.scripts {
|
|
|
|
|
context.builder.WriteString(script)
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
2024-09-22 15:46:38 +00:00
|
|
|
context.scripts = []string{}
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
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-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-22 15:46:38 +00:00
|
|
|
func (t *TextContent) Render(context *RenderContext) {
|
2024-09-29 01:45:27 +00:00
|
|
|
context.builder.WriteString(template.HTMLEscapeString(t.Content))
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-22 15:46:38 +00:00
|
|
|
func (r *RawContent) Render(context *RenderContext) {
|
|
|
|
|
context.builder.WriteString(r.Content)
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-22 15:46:38 +00:00
|
|
|
func (c *ChildList) Render(context *RenderContext) {
|
2024-09-20 01:24:44 +00:00
|
|
|
for _, child := range c.Children {
|
2024-09-22 15:46:38 +00:00
|
|
|
child.Render(context)
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
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) {
|
2024-09-20 01:24:44 +00:00
|
|
|
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-09-22 16:53:41 +00:00
|
|
|
m[event] += fmt.Sprintf("%s;", c.Command)
|
2024-09-22 15:46:38 +00:00
|
|
|
case ComplexJsCommand:
|
|
|
|
|
context.AddScript(c.TempFuncName, c.Command)
|
2024-09-25 21:57:13 +00:00
|
|
|
m[event] += fmt.Sprintf("%s(this);", 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
|
|
|
}
|
|
|
|
|
|
2024-09-20 01:24:44 +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-20 01:24:44 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-22 15:46:38 +00:00
|
|
|
Children(children...).Render(context)
|
2024-09-20 01:24:44 +00:00
|
|
|
}
|