update attrs to use ordered map
This commit is contained in:
parent
0fa096ea2f
commit
7b83e2fde7
12 changed files with 311 additions and 193 deletions
|
|
@ -17,6 +17,12 @@ func HasFileFromRoot(file string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
func CreateHtmgoDir() {
|
||||
if !HasFileFromRoot("__htmgo") {
|
||||
CreateDirFromRoot("__htmgo")
|
||||
}
|
||||
}
|
||||
|
||||
func CreateDirFromRoot(dir string) error {
|
||||
cwd := process.GetWorkingDir()
|
||||
path := filepath.Join(cwd, dir)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"github.com/maddalax/htmgo/cli/htmgo/internal"
|
||||
"github.com/maddalax/htmgo/cli/htmgo/internal/dirutil"
|
||||
"github.com/maddalax/htmgo/cli/htmgo/tasks/astgen"
|
||||
"github.com/maddalax/htmgo/cli/htmgo/tasks/copyassets"
|
||||
"github.com/maddalax/htmgo/cli/htmgo/tasks/css"
|
||||
|
|
@ -57,10 +56,6 @@ func main() {
|
|||
slog.Debug("Running task:", slog.String("task", taskName))
|
||||
slog.Debug("working dir:", slog.String("dir", process.GetWorkingDir()))
|
||||
|
||||
if !dirutil.HasFileFromRoot("__htmgo") {
|
||||
dirutil.CreateDirFromRoot("__htmgo")
|
||||
}
|
||||
|
||||
if taskName == "watch" {
|
||||
fmt.Printf("Running in watch mode\n")
|
||||
os.Setenv("ENV", "development")
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ func getModuleVersion(modulePath string) (string, error) {
|
|||
}
|
||||
|
||||
func CopyAssets() {
|
||||
dirutil.CreateHtmgoDir()
|
||||
|
||||
moduleName := "github.com/maddalax/htmgo/framework"
|
||||
modulePath := module.GetDependencyPath(moduleName)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,27 +3,50 @@ package h
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
"github.com/maddalax/htmgo/framework/internal/datastructure"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AttributeMap map[string]any
|
||||
type AttributeMap = map[string]any
|
||||
|
||||
func (m *AttributeMap) ToMap() map[string]string {
|
||||
result := make(map[string]string)
|
||||
for k, v := range *m {
|
||||
switch v.(type) {
|
||||
case AttributeMap:
|
||||
m2 := v.(*AttributeMap).ToMap()
|
||||
for _, a := range m2 {
|
||||
result[k] = a
|
||||
type AttributeMapOrdered struct {
|
||||
data *datastructure.OrderedMap[string, string]
|
||||
}
|
||||
|
||||
func (m *AttributeMapOrdered) Set(key string, value any) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
result[k] = v.(string)
|
||||
m.data.Set(key, v)
|
||||
case *AttributeMapOrdered:
|
||||
v.Each(func(k string, v any) {
|
||||
m.Set(k, v)
|
||||
})
|
||||
case *AttributeR:
|
||||
m.data.Set(v.Name, v.Value)
|
||||
default:
|
||||
result[k] = fmt.Sprintf("%v", v)
|
||||
m.data.Set(key, fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
func (m *AttributeMapOrdered) Each(cb func(key string, value any)) {
|
||||
m.data.Each(func(key string, value string) {
|
||||
cb(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *AttributeMapOrdered) Entries() []datastructure.MapEntry[string, string] {
|
||||
return m.data.Entries()
|
||||
}
|
||||
|
||||
func NewAttributeMap(pairs ...string) *AttributeMapOrdered {
|
||||
m := datastructure.NewOrderedMap[string, string]()
|
||||
if len(pairs)%2 == 0 {
|
||||
for i := 0; i < len(pairs); i++ {
|
||||
m.Set(pairs[i], pairs[i+1])
|
||||
i++
|
||||
}
|
||||
}
|
||||
return &AttributeMapOrdered{data: m}
|
||||
}
|
||||
|
||||
func Attribute(key string, value string) *AttributeR {
|
||||
|
|
@ -33,28 +56,24 @@ func Attribute(key string, value string) *AttributeR {
|
|||
}
|
||||
}
|
||||
|
||||
func AttributeList(children ...*AttributeR) *AttributeMap {
|
||||
m := make(AttributeMap)
|
||||
for _, child := range children {
|
||||
m[child.Name] = child.Value
|
||||
func AttributeList(children ...*AttributeR) *AttributeMapOrdered {
|
||||
m := NewAttributeMap()
|
||||
for _, c := range children {
|
||||
m.Set(c.Name, c.Value)
|
||||
}
|
||||
return &m
|
||||
return m
|
||||
}
|
||||
|
||||
func Attributes(attrs *AttributeMap) *AttributeMap {
|
||||
return attrs
|
||||
func Attributes(attributes *AttributeMap) *AttributeMapOrdered {
|
||||
m := NewAttributeMap()
|
||||
for k, v := range *attributes {
|
||||
m.Set(k, v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func AttributePairs(pairs ...string) *AttributeMap {
|
||||
if len(pairs)%2 != 0 {
|
||||
return &AttributeMap{}
|
||||
}
|
||||
m := make(AttributeMap)
|
||||
for i := 0; i < len(pairs); i++ {
|
||||
m[pairs[i]] = pairs[i+1]
|
||||
i++
|
||||
}
|
||||
return &m
|
||||
func AttributePairs(pairs ...string) *AttributeMapOrdered {
|
||||
return NewAttributeMap(pairs...)
|
||||
}
|
||||
|
||||
func Checked() Ren {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func validateCommands(cmds []Command) {
|
|||
break
|
||||
case ComplexJsCommand:
|
||||
break
|
||||
case *AttributeMap:
|
||||
case *AttributeMapOrdered:
|
||||
break
|
||||
case *Element:
|
||||
panic(fmt.Sprintf("element is not allowed in lifecycle events. Got: %v", t))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package h
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/html"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -13,43 +10,12 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Sort attributes of a node by attribute name
|
||||
func sortAttributes(node *html.Node) {
|
||||
if node.Type == html.ElementNode && len(node.Attr) > 1 {
|
||||
sort.SliceStable(node.Attr, func(i, j int) bool {
|
||||
return node.Attr[i].Key < node.Attr[j].Key
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse and sort attributes in the entire HTML tree
|
||||
func traverseAndSortAttributes(node *html.Node) {
|
||||
sortAttributes(node)
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
traverseAndSortAttributes(child)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse HTML, sort attributes, and render back to a string
|
||||
func sortHtmlAttributes(input string) string {
|
||||
// Parse the HTML string into a node tree
|
||||
doc, err := html.Parse(strings.NewReader(input))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Traverse and sort attributes for each node
|
||||
traverseAndSortAttributes(doc)
|
||||
|
||||
// Use a buffer to capture the rendered HTML
|
||||
var buf bytes.Buffer
|
||||
err = html.Render(&buf, doc)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Return the rendered HTML as a string
|
||||
return buf.String()
|
||||
func TestSimpleRender(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := Render(
|
||||
Div(Attribute("id", "my-div"), Attribute("class", "my-class")),
|
||||
)
|
||||
assert.Equal(t, `<div id="my-div" class="my-class"></div>`, result)
|
||||
}
|
||||
|
||||
func TestRender(t *testing.T) {
|
||||
|
|
@ -57,10 +23,10 @@ func TestRender(t *testing.T) {
|
|||
div := Div(
|
||||
Id("my-div"),
|
||||
Attribute("data-attr-2", "value"),
|
||||
Attributes(&AttributeMap{
|
||||
"data-attr-3": "value",
|
||||
"data-attr-4": "value",
|
||||
}),
|
||||
AttributePairs(
|
||||
"data-attr-3", "value",
|
||||
"data-attr-4", "value",
|
||||
),
|
||||
HxBeforeRequest(
|
||||
SetText("before request"),
|
||||
),
|
||||
|
|
@ -73,17 +39,31 @@ func TestRender(t *testing.T) {
|
|||
Text("hello, child"),
|
||||
)
|
||||
|
||||
div.attributes["data-attr-1"] = "value"
|
||||
div.attributes.Set("data-attr-1", "value")
|
||||
|
||||
expectedRaw := `<div data-attr-1="value" id="my-div" data-attr-2="value" data-attr-3="value" data-attr-4="value" hx-on::before-request="this.innerText = 'before request';" hx-on::after-request="this.innerText = 'after request';"><div >hello, world</div>hello, child</div>`
|
||||
expected := sortHtmlAttributes(expectedRaw)
|
||||
result := sortHtmlAttributes(Render(div))
|
||||
expected := `<div data-attr-1="value" id="my-div" data-attr-2="value" data-attr-3="value" data-attr-4="value" hx-on::before-request="this.innerText = 'before request';" hx-on::after-request="this.innerText = 'after request';"><div>hello, world</div>hello, child</div>`
|
||||
result := Render(div)
|
||||
|
||||
assert.Equal(t,
|
||||
expected,
|
||||
result)
|
||||
}
|
||||
|
||||
func TestRenderAttributes_1(t *testing.T) {
|
||||
t.Parallel()
|
||||
div := Div(
|
||||
AttributePairs("class", "bg-red-500"),
|
||||
Attributes(&AttributeMap{
|
||||
"id": Id("my-div"),
|
||||
}),
|
||||
Attribute("disabled", "true"),
|
||||
)
|
||||
assert.Equal(t,
|
||||
`<div class="bg-red-500" id="my-div" disabled="true"></div>`,
|
||||
Render(div),
|
||||
)
|
||||
}
|
||||
|
||||
func TestRawContent(t *testing.T) {
|
||||
t.Parallel()
|
||||
str := "<div>hello, world</div>"
|
||||
|
|
@ -134,12 +114,12 @@ func TestCached(t *testing.T) {
|
|||
return ComplexPage()
|
||||
})
|
||||
|
||||
firstRender := sortHtmlAttributes(Render(page()))
|
||||
secondRender := sortHtmlAttributes(Render(page()))
|
||||
firstRender := Render(page())
|
||||
secondRender := Render(page())
|
||||
|
||||
assert.Equal(t, firstRender, secondRender)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Equal(t, firstRender, sortHtmlAttributes(Render(ComplexPage())))
|
||||
assert.Equal(t, firstRender, Render(ComplexPage()))
|
||||
}
|
||||
|
||||
func TestCachedT(t *testing.T) {
|
||||
|
|
@ -150,12 +130,12 @@ func TestCachedT(t *testing.T) {
|
|||
return ComplexPage()
|
||||
})
|
||||
|
||||
firstRender := sortHtmlAttributes(Render(page("a")))
|
||||
secondRender := sortHtmlAttributes(Render(page("a")))
|
||||
firstRender := Render(page("a"))
|
||||
secondRender := Render(page("a"))
|
||||
|
||||
assert.Equal(t, firstRender, secondRender)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Equal(t, firstRender, sortHtmlAttributes(Render(ComplexPage())))
|
||||
assert.Equal(t, firstRender, Render(ComplexPage()))
|
||||
}
|
||||
|
||||
func TestCachedT2(t *testing.T) {
|
||||
|
|
@ -166,12 +146,12 @@ func TestCachedT2(t *testing.T) {
|
|||
return ComplexPage()
|
||||
})
|
||||
|
||||
firstRender := sortHtmlAttributes(Render(page("a", "b")))
|
||||
secondRender := sortHtmlAttributes(Render(page("a", "b")))
|
||||
firstRender := Render(page("a", "b"))
|
||||
secondRender := Render(page("a", "b"))
|
||||
|
||||
assert.Equal(t, firstRender, secondRender)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Equal(t, firstRender, sortHtmlAttributes(Render(ComplexPage())))
|
||||
assert.Equal(t, firstRender, Render(ComplexPage()))
|
||||
}
|
||||
|
||||
func TestCachedT3(t *testing.T) {
|
||||
|
|
@ -182,12 +162,12 @@ func TestCachedT3(t *testing.T) {
|
|||
return ComplexPage()
|
||||
})
|
||||
|
||||
firstRender := sortHtmlAttributes(Render(page("a", "b", "c")))
|
||||
secondRender := sortHtmlAttributes(Render(page("a", "b", "c")))
|
||||
firstRender := Render(page("a", "b", "c"))
|
||||
secondRender := Render(page("a", "b", "c"))
|
||||
|
||||
assert.Equal(t, firstRender, secondRender)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Equal(t, firstRender, sortHtmlAttributes(Render(ComplexPage())))
|
||||
assert.Equal(t, firstRender, Render(ComplexPage()))
|
||||
}
|
||||
|
||||
func TestCachedT4(t *testing.T) {
|
||||
|
|
@ -198,12 +178,12 @@ func TestCachedT4(t *testing.T) {
|
|||
return ComplexPage()
|
||||
})
|
||||
|
||||
firstRender := sortHtmlAttributes(Render(page("a", "b", "c", "d")))
|
||||
secondRender := sortHtmlAttributes(Render(page("a", "b", "c", "d")))
|
||||
firstRender := Render(page("a", "b", "c", "d"))
|
||||
secondRender := Render(page("a", "b", "c", "d"))
|
||||
|
||||
assert.Equal(t, firstRender, secondRender)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Equal(t, firstRender, sortHtmlAttributes(Render(ComplexPage())))
|
||||
assert.Equal(t, firstRender, Render(ComplexPage()))
|
||||
}
|
||||
|
||||
func TestCachedExpired(t *testing.T) {
|
||||
|
|
@ -214,9 +194,9 @@ func TestCachedExpired(t *testing.T) {
|
|||
return ComplexPage()
|
||||
})
|
||||
|
||||
firstRender := sortHtmlAttributes(Render(page()))
|
||||
firstRender := Render(page())
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
secondRender := sortHtmlAttributes(Render(page()))
|
||||
secondRender := Render(page())
|
||||
|
||||
assert.Equal(t, firstRender, secondRender)
|
||||
assert.Equal(t, 2, count)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ type AttributeR struct {
|
|||
Value string
|
||||
}
|
||||
|
||||
type KeyValue[T any] struct {
|
||||
Key string
|
||||
Value T
|
||||
}
|
||||
|
||||
type TextContent struct {
|
||||
Content string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ var voidTags = map[string]bool{
|
|||
type RenderContext struct {
|
||||
builder *strings.Builder
|
||||
scripts []string
|
||||
next any
|
||||
prev any
|
||||
}
|
||||
|
||||
func (ctx *RenderContext) PrevIsAttribute() bool {
|
||||
_, ok := ctx.prev.(*AttributeR)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ctx *RenderContext) AddScript(funcName string, body string) {
|
||||
|
|
@ -50,6 +57,29 @@ func (ctx *RenderContext) AddScript(funcName string, body string) {
|
|||
ctx.scripts = append(ctx.scripts, script)
|
||||
}
|
||||
|
||||
func each[T any](ctx *RenderContext, arr []T, cb func(T)) {
|
||||
for i, r := range arr {
|
||||
if i == len(arr)-1 {
|
||||
ctx.next = nil
|
||||
} else {
|
||||
ctx.next = arr[i+1]
|
||||
}
|
||||
cb(r)
|
||||
}
|
||||
}
|
||||
|
||||
func eachAttrMap(ctx *RenderContext, m *AttributeMapOrdered, cb func(string, string)) {
|
||||
entries := m.Entries()
|
||||
for i, entry := range entries {
|
||||
if i == len(entries)-1 {
|
||||
ctx.next = nil
|
||||
} else {
|
||||
ctx.next = entries[i+1]
|
||||
}
|
||||
cb(entry.Key, entry.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func (node *Element) Render(context *RenderContext) {
|
||||
// some elements may not have a tag, such as a Fragment
|
||||
|
||||
|
|
@ -68,11 +98,9 @@ func (node *Element) Render(context *RenderContext) {
|
|||
if node.tag != "" {
|
||||
context.builder.WriteString("<")
|
||||
context.builder.WriteString(node.tag)
|
||||
context.builder.WriteString(" ")
|
||||
|
||||
for name, value := range node.attributes {
|
||||
NewAttribute(name, value).Render(context)
|
||||
}
|
||||
eachAttrMap(context, node.attributes, func(key string, value string) {
|
||||
NewAttribute(key, value).Render(context)
|
||||
})
|
||||
}
|
||||
|
||||
totalChildren := 0
|
||||
|
|
@ -110,7 +138,7 @@ func (node *Element) Render(context *RenderContext) {
|
|||
// second pass, render any attributes within the tag
|
||||
for _, child := range node.children {
|
||||
switch child.(type) {
|
||||
case *AttributeMap:
|
||||
case *AttributeMapOrdered:
|
||||
child.Render(context)
|
||||
case *AttributeR:
|
||||
child.Render(context)
|
||||
|
|
@ -132,7 +160,7 @@ func (node *Element) Render(context *RenderContext) {
|
|||
// render the children elements that are not attributes
|
||||
for _, child := range node.children {
|
||||
switch child.(type) {
|
||||
case *AttributeMap:
|
||||
case *AttributeMapOrdered:
|
||||
continue
|
||||
case *AttributeR:
|
||||
continue
|
||||
|
|
@ -157,55 +185,59 @@ func (node *Element) Render(context *RenderContext) {
|
|||
func renderScripts(context *RenderContext) {
|
||||
for _, script := range context.scripts {
|
||||
context.builder.WriteString(script)
|
||||
context.prev = script
|
||||
}
|
||||
context.scripts = []string{}
|
||||
}
|
||||
|
||||
func (a *AttributeR) Render(context *RenderContext) {
|
||||
context.builder.WriteString(" ")
|
||||
context.builder.WriteString(a.Name)
|
||||
if a.Value != "" {
|
||||
context.builder.WriteString(`=`)
|
||||
context.builder.WriteString(`"`)
|
||||
context.builder.WriteString(html.EscapeString(a.Value))
|
||||
context.builder.WriteString(`"`)
|
||||
} else {
|
||||
context.builder.WriteString(" ")
|
||||
}
|
||||
context.prev = a
|
||||
}
|
||||
|
||||
func (t *TextContent) Render(context *RenderContext) {
|
||||
context.builder.WriteString(template.HTMLEscapeString(t.Content))
|
||||
context.prev = t
|
||||
}
|
||||
|
||||
func (r *RawContent) Render(context *RenderContext) {
|
||||
context.builder.WriteString(r.Content)
|
||||
context.prev = r
|
||||
}
|
||||
|
||||
func (c *ChildList) Render(context *RenderContext) {
|
||||
for _, child := range c.Children {
|
||||
child.Render(context)
|
||||
context.prev = child
|
||||
}
|
||||
}
|
||||
|
||||
func (j SimpleJsCommand) Render(context *RenderContext) {
|
||||
context.builder.WriteString(j.Command)
|
||||
context.prev = j
|
||||
}
|
||||
|
||||
func (j ComplexJsCommand) Render(context *RenderContext) {
|
||||
context.builder.WriteString(j.Command)
|
||||
context.prev = j
|
||||
}
|
||||
|
||||
func (p *Partial) Render(context *RenderContext) {
|
||||
p.Root.Render(context)
|
||||
context.prev = p
|
||||
}
|
||||
|
||||
func (m *AttributeMap) Render(context *RenderContext) {
|
||||
m2 := m.ToMap()
|
||||
|
||||
for k, v := range m2 {
|
||||
context.builder.WriteString(" ")
|
||||
NewAttribute(k, v).Render(context)
|
||||
}
|
||||
func (m *AttributeMapOrdered) Render(context *RenderContext) {
|
||||
eachAttrMap(context, m, func(key string, value string) {
|
||||
NewAttribute(key, value).Render(context)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *LifeCycle) fromAttributeMap(event string, key string, value string, context *RenderContext) {
|
||||
|
|
@ -222,21 +254,21 @@ func (l *LifeCycle) Render(context *RenderContext) {
|
|||
|
||||
for event, commands := range l.handlers {
|
||||
m[event] = ""
|
||||
for _, command := range commands {
|
||||
each(context, commands, func(command Command) {
|
||||
switch c := command.(type) {
|
||||
case SimpleJsCommand:
|
||||
m[event] += fmt.Sprintf("%s;", c.Command)
|
||||
case ComplexJsCommand:
|
||||
context.AddScript(c.TempFuncName, c.Command)
|
||||
m[event] += fmt.Sprintf("%s(this);", c.TempFuncName)
|
||||
case *AttributeMap:
|
||||
for k, v := range c.ToMap() {
|
||||
case *AttributeMapOrdered:
|
||||
eachAttrMap(context, c, func(k string, v string) {
|
||||
l.fromAttributeMap(event, k, v, context)
|
||||
}
|
||||
})
|
||||
case *AttributeR:
|
||||
l.fromAttributeMap(event, c.Name, c.Value, context)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
children := make([]Ren, 0)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ type PartialFunc = func(ctx *RequestContext) *Partial
|
|||
|
||||
type Element struct {
|
||||
tag string
|
||||
attributes map[string]string
|
||||
attributes *AttributeMapOrdered
|
||||
meta any
|
||||
children []Ren
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ func Tag(tag string, children ...Ren) *Element {
|
|||
return &Element{
|
||||
tag: tag,
|
||||
children: children,
|
||||
attributes: make(map[string]string),
|
||||
attributes: NewAttributeMap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,56 +69,52 @@ func Body(children ...Ren) *Element {
|
|||
func Meta(name string, content string) *Element {
|
||||
return &Element{
|
||||
tag: "meta",
|
||||
attributes: map[string]string{
|
||||
"name": name,
|
||||
"content": content,
|
||||
},
|
||||
attributes: AttributePairs(
|
||||
"name", name,
|
||||
"content", content,
|
||||
),
|
||||
children: make([]Ren, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func LinkWithVersion(href string, rel string, version string) *Element {
|
||||
attributeMap := AttributeMap{
|
||||
"href": href + "?v=" + version,
|
||||
"rel": rel,
|
||||
}
|
||||
return &Element{
|
||||
tag: "link",
|
||||
attributes: attributeMap.ToMap(),
|
||||
attributes: AttributePairs(
|
||||
"href", href+"?v="+version,
|
||||
"rel", rel,
|
||||
),
|
||||
children: make([]Ren, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func Link(href string, rel string) *Element {
|
||||
attributeMap := AttributeMap{
|
||||
"href": href,
|
||||
"rel": rel,
|
||||
}
|
||||
return &Element{
|
||||
tag: "link",
|
||||
attributes: attributeMap.ToMap(),
|
||||
attributes: AttributePairs(
|
||||
"href", href,
|
||||
"rel", rel,
|
||||
),
|
||||
children: make([]Ren, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func ScriptWithVersion(url string, version string) *Element {
|
||||
attributeMap := AttributeMap{
|
||||
"src": url + "?v=" + version,
|
||||
}
|
||||
return &Element{
|
||||
tag: "script",
|
||||
attributes: attributeMap.ToMap(),
|
||||
attributes: AttributePairs(
|
||||
"src", url+"?v="+version,
|
||||
),
|
||||
children: make([]Ren, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func Script(url string) *Element {
|
||||
attributeMap := AttributeMap{
|
||||
"src": url,
|
||||
}
|
||||
return &Element{
|
||||
tag: "script",
|
||||
attributes: attributeMap.ToMap(),
|
||||
attributes: AttributePairs(
|
||||
"src", url,
|
||||
),
|
||||
children: make([]Ren, 0),
|
||||
}
|
||||
}
|
||||
|
|
@ -177,12 +173,11 @@ func Value(value any) *AttributeR {
|
|||
}
|
||||
|
||||
func Input(inputType string, children ...Ren) *Element {
|
||||
attributeMap := AttributeMap{
|
||||
"type": inputType,
|
||||
}
|
||||
return &Element{
|
||||
tag: "input",
|
||||
attributes: attributeMap.ToMap(),
|
||||
attributes: AttributePairs(
|
||||
"type", inputType,
|
||||
),
|
||||
children: children,
|
||||
}
|
||||
}
|
||||
|
|
@ -257,7 +252,7 @@ func TagF(tag string, format string, args ...interface{}) *Element {
|
|||
An invocation can look like
|
||||
h.H3F("build simple and scalable systems with %s", "go + htmx", h.Class("-mt-4")),
|
||||
|
||||
where the args may be a mix of strings, *Element, *AttributeMap, *ChildList, *AttributeR
|
||||
where the args may be a mix of strings, *Element, *AttributeMapOrdered, *ChildList, *AttributeR
|
||||
We need to separate the children from the format arguments
|
||||
*/
|
||||
children := make([]Ren, 0)
|
||||
|
|
@ -266,7 +261,7 @@ func TagF(tag string, format string, args ...interface{}) *Element {
|
|||
switch d := arg.(type) {
|
||||
case *Element:
|
||||
children = append(children, d)
|
||||
case *AttributeMap:
|
||||
case *AttributeMapOrdered:
|
||||
children = append(children, d)
|
||||
case *ChildList:
|
||||
for _, child := range d.Children {
|
||||
|
|
|
|||
|
|
@ -2,46 +2,46 @@ package h
|
|||
|
||||
import "github.com/maddalax/htmgo/framework/hx"
|
||||
|
||||
func Get(path string, trigger ...string) *AttributeMap {
|
||||
func Get(path string, trigger ...string) *AttributeMapOrdered {
|
||||
return AttributeList(Attribute(hx.GetAttr, path), HxTriggerString(trigger...))
|
||||
}
|
||||
|
||||
func GetPartial(partial PartialFunc, trigger ...string) *AttributeMap {
|
||||
func GetPartial(partial PartialFunc, trigger ...string) *AttributeMapOrdered {
|
||||
return Get(GetPartialPath(partial), trigger...)
|
||||
}
|
||||
|
||||
func GetPartialWithQs(partial PartialFunc, qs *Qs, trigger string) *AttributeMap {
|
||||
func GetPartialWithQs(partial PartialFunc, qs *Qs, trigger string) *AttributeMapOrdered {
|
||||
return Get(GetPartialPathWithQs(partial, qs), trigger)
|
||||
}
|
||||
|
||||
func GetWithQs(path string, qs *Qs, trigger string) *AttributeMap {
|
||||
func GetWithQs(path string, qs *Qs, trigger string) *AttributeMapOrdered {
|
||||
return Get(SetQueryParams(path, qs), trigger)
|
||||
}
|
||||
|
||||
func PostPartial(partial PartialFunc, triggers ...string) *AttributeMap {
|
||||
func PostPartial(partial PartialFunc, triggers ...string) *AttributeMapOrdered {
|
||||
return Post(GetPartialPath(partial), triggers...)
|
||||
}
|
||||
|
||||
func PostPartialWithQs(partial PartialFunc, qs *Qs, trigger ...string) *AttributeMap {
|
||||
func PostPartialWithQs(partial PartialFunc, qs *Qs, trigger ...string) *AttributeMapOrdered {
|
||||
return Post(GetPartialPathWithQs(partial, qs), trigger...)
|
||||
}
|
||||
|
||||
func Post(url string, trigger ...string) *AttributeMap {
|
||||
func Post(url string, trigger ...string) *AttributeMapOrdered {
|
||||
return AttributeList(Attribute(hx.PostAttr, url), HxTriggerString(trigger...))
|
||||
}
|
||||
|
||||
func PostWithQs(url string, qs *Qs, trigger string) *AttributeMap {
|
||||
func PostWithQs(url string, qs *Qs, trigger string) *AttributeMapOrdered {
|
||||
return Post(SetQueryParams(url, qs), trigger)
|
||||
}
|
||||
|
||||
func PostOnClick(url string) *AttributeMap {
|
||||
func PostOnClick(url string) *AttributeMapOrdered {
|
||||
return Post(url, hx.ClickEvent)
|
||||
}
|
||||
|
||||
func PostPartialOnClick(partial PartialFunc) *AttributeMap {
|
||||
func PostPartialOnClick(partial PartialFunc) *AttributeMapOrdered {
|
||||
return PostOnClick(GetPartialPath(partial))
|
||||
}
|
||||
|
||||
func PostPartialOnClickQs(partial PartialFunc, qs *Qs) *AttributeMap {
|
||||
func PostPartialOnClickQs(partial PartialFunc, qs *Qs) *AttributeMapOrdered {
|
||||
return PostOnClick(GetPartialPathWithQs(partial, qs))
|
||||
}
|
||||
|
|
|
|||
84
framework/internal/datastructure/map.go
Normal file
84
framework/internal/datastructure/map.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package datastructure
|
||||
|
||||
type MapEntry[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// OrderedMap is a generic data structure that maintains the order of keys.
|
||||
type OrderedMap[K comparable, V any] struct {
|
||||
keys []K
|
||||
values map[K]V
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) Each(cb func(key K, value V)) {
|
||||
for _, key := range om.keys {
|
||||
cb(key, om.values[key])
|
||||
}
|
||||
}
|
||||
|
||||
// Entries returns the key-value pairs in the order they were added.
|
||||
func (om *OrderedMap[K, V]) Entries() []MapEntry[K, V] {
|
||||
entries := make([]MapEntry[K, V], len(om.keys))
|
||||
for i, key := range om.keys {
|
||||
entries[i] = MapEntry[K, V]{
|
||||
Key: key,
|
||||
Value: om.values[key],
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// NewOrderedMap creates a new OrderedMap.
|
||||
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
|
||||
return &OrderedMap[K, V]{
|
||||
keys: []K{},
|
||||
values: make(map[K]V),
|
||||
}
|
||||
}
|
||||
|
||||
// Set adds or updates a key-value pair in the OrderedMap.
|
||||
func (om *OrderedMap[K, V]) Set(key K, value V) {
|
||||
// Check if the key already exists
|
||||
if _, exists := om.values[key]; !exists {
|
||||
om.keys = append(om.keys, key) // Append key to the keys slice if it's a new key
|
||||
}
|
||||
om.values[key] = value
|
||||
}
|
||||
|
||||
// Get retrieves a value by key.
|
||||
func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
|
||||
value, exists := om.values[key]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// Keys returns the keys in the order they were added.
|
||||
func (om *OrderedMap[K, V]) Keys() []K {
|
||||
return om.keys
|
||||
}
|
||||
|
||||
// Values returns the values in the order of their keys.
|
||||
func (om *OrderedMap[K, V]) Values() []V {
|
||||
values := make([]V, len(om.keys))
|
||||
for i, key := range om.keys {
|
||||
values[i] = om.values[key]
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Delete removes a key-value pair from the OrderedMap.
|
||||
func (om *OrderedMap[K, V]) Delete(key K) {
|
||||
if _, exists := om.values[key]; exists {
|
||||
// Remove the key from the map
|
||||
delete(om.values, key)
|
||||
|
||||
// Remove the key from the keys slice
|
||||
for i, k := range om.keys {
|
||||
if k == key {
|
||||
om.keys = append(om.keys[:i], om.keys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue