From 290ccc81da7a02e3a0763db7302c58c23f1151a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20W=C3=BCstenberg?= Date: Tue, 12 Dec 2023 13:39:23 +0100 Subject: [PATCH 1/4] Add Lazy function `Lazy` provides lazy evaluation of node rendering. Useful together with `If`. Fixes #152 --- gomponents.go | 42 +++++++++++++++++++++++++++++++ gomponents_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/gomponents.go b/gomponents.go index 7b73a3b..55f1521 100644 --- a/gomponents.go +++ b/gomponents.go @@ -260,3 +260,45 @@ func If(condition bool, n Node) Node { } return nil } + +// Lazy returns a Node which calls the given callback function only when rendered. +// Used with If, it enables lazy evaluation of the node to return, depending on the condition passed to If. +// It defaults to being an ElementType, but can be set to AttributeType by passing a NodeType. +// Even though nodeTypes is variadic, only the first NodeType is used. +func Lazy(cb func() Node, nodeType ...NodeType) Node { + if cb == nil { + panic("lazy callback cannot be nil") + } + + switch len(nodeType) { + case 0: + return &lazy{CB: cb} + case 1: + return &lazy{CB: cb, T: nodeType[0]} + default: + panic("lazy only accepts one node type") + } +} + +// Lazy is returned by Lazy. +type lazy struct { + CB func() Node + T NodeType +} + +// Render satisfies Node. +func (l *lazy) Render(w io.Writer) error { + return l.CB().Render(w) +} + +// Type satisfies nodeTypeDescriber. +func (l *lazy) Type() NodeType { + return l.T +} + +// String satisfies fmt.Stringer. +func (l *lazy) String() string { + var b strings.Builder + _ = l.Render(&b) + return b.String() +} diff --git a/gomponents_test.go b/gomponents_test.go index 32304f3..c0b22de 100644 --- a/gomponents_test.go +++ b/gomponents_test.go @@ -284,3 +284,64 @@ func ExampleIf() { _ = e.Render(os.Stdout) // Output:
You lost your hat!
} + +func TestLazy(t *testing.T) { + t.Run("panics when callback is nil", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.FailNow() + } + }() + g.Lazy(nil) + }) + + t.Run("calls callback on render", func(t *testing.T) { + n := g.Lazy(func() g.Node { + return g.El("div") + }) + assert.Equal(t, "
", n) + }) + + t.Run("can render elements with node type passed", func(t *testing.T) { + n := g.El("div", g.Lazy(func() g.Node { + return g.El("span") + }, g.ElementType)) + assert.Equal(t, "
", n) + }) + + t.Run("can render attributes", func(t *testing.T) { + n := g.El("input", g.Lazy(func() g.Node { + return g.Attr("disabled") + }, g.AttributeType)) + assert.Equal(t, "", n) + }) + + t.Run("panics when passed multiple node types", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.FailNow() + } + }() + g.Lazy(func() g.Node { + return nil + }, g.ElementType, g.AttributeType) + }) +} + +func ExampleIf_lazy() { + type User struct { + Name string + } + + var u *User + + e := g.El("div", + // This would panic without the Lazy call. + g.If(u != nil, g.Lazy(func() g.Node { + return g.El("span", g.Text(u.Name)) + })), + g.If(u == nil, g.El("span", g.Text("Anonymous"))), + ) + _ = e.Render(os.Stdout) + // Output:
Anonymous
+} From 0466b3a1c2f4e29adbbf3c3f2f3709f51a8d234e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20W=C3=BCstenberg?= Date: Tue, 12 Dec 2023 13:44:23 +0100 Subject: [PATCH 2/4] Add fmt.Stringer test --- gomponents_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gomponents_test.go b/gomponents_test.go index c0b22de..f6c99de 100644 --- a/gomponents_test.go +++ b/gomponents_test.go @@ -326,6 +326,15 @@ func TestLazy(t *testing.T) { return nil }, g.ElementType, g.AttributeType) }) + + t.Run("satisfies fmt.Stringer", func(t *testing.T) { + n := g.Lazy(func() g.Node { + return g.El("div") + }) + if _, ok := n.(fmt.Stringer); !ok { + t.FailNow() + } + }) } func ExampleIf_lazy() { From a6b85d1aeb65a28c262cced5f4d075c9877c59ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20W=C3=BCstenberg?= Date: Tue, 12 Dec 2023 13:53:10 +0100 Subject: [PATCH 3/4] Remove fmt.Stringer support --- gomponents.go | 7 ------- gomponents_test.go | 9 --------- 2 files changed, 16 deletions(-) diff --git a/gomponents.go b/gomponents.go index 55f1521..ac28d3d 100644 --- a/gomponents.go +++ b/gomponents.go @@ -295,10 +295,3 @@ func (l *lazy) Render(w io.Writer) error { func (l *lazy) Type() NodeType { return l.T } - -// String satisfies fmt.Stringer. -func (l *lazy) String() string { - var b strings.Builder - _ = l.Render(&b) - return b.String() -} diff --git a/gomponents_test.go b/gomponents_test.go index f6c99de..c0b22de 100644 --- a/gomponents_test.go +++ b/gomponents_test.go @@ -326,15 +326,6 @@ func TestLazy(t *testing.T) { return nil }, g.ElementType, g.AttributeType) }) - - t.Run("satisfies fmt.Stringer", func(t *testing.T) { - n := g.Lazy(func() g.Node { - return g.El("div") - }) - if _, ok := n.(fmt.Stringer); !ok { - t.FailNow() - } - }) } func ExampleIf_lazy() { From f83f6cd8f92281c177be1d3bc934c290f801ca51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20W=C3=BCstenberg?= Date: Wed, 13 Dec 2023 11:08:50 +0100 Subject: [PATCH 4/4] Add more docs --- gomponents.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gomponents.go b/gomponents.go index ac28d3d..cb59964 100644 --- a/gomponents.go +++ b/gomponents.go @@ -254,6 +254,9 @@ func Group(children []Node) Node { // If condition is true, return the given Node. Otherwise, return nil. // This helper function is good for inlining elements conditionally. +// This doesn't behave entirely like a regular if statement, because nested Nodes and their parameters are evaluated +// from the deepest level and up before hitting If (because that's how Go works). +// If you need lazy evaluation of the given Node, see Lazy. func If(condition bool, n Node) Node { if condition { return n @@ -264,7 +267,7 @@ func If(condition bool, n Node) Node { // Lazy returns a Node which calls the given callback function only when rendered. // Used with If, it enables lazy evaluation of the node to return, depending on the condition passed to If. // It defaults to being an ElementType, but can be set to AttributeType by passing a NodeType. -// Even though nodeTypes is variadic, only the first NodeType is used. +// Even though nodeType is variadic, only the first NodeType is used. func Lazy(cb func() Node, nodeType ...NodeType) Node { if cb == nil { panic("lazy callback cannot be nil")