From 821671c012499c6c74a71c5cad629ef633146e24 Mon Sep 17 00:00:00 2001 From: Arman Date: Mon, 9 Sep 2024 20:51:55 +0100 Subject: [PATCH 1/7] WIP templ Join API, pass in children templates and render them in one templ.Component --- join.go | 18 ++++++++++++++++ join_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 join.go create mode 100644 join_test.go diff --git a/join.go b/join.go new file mode 100644 index 000000000..525e4364f --- /dev/null +++ b/join.go @@ -0,0 +1,18 @@ +package templ + +import ( + "context" + "io" +) + +//Pass any number of templ.Components to get a single templ.Component with the components rendered +func Join(components ...Component) Component { + return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { + for _, c := range components { + if err = c.Render(ctx, w); err != nil { + return err + } + } + return nil + }) +} diff --git a/join_test.go b/join_test.go new file mode 100644 index 000000000..c98d67c6a --- /dev/null +++ b/join_test.go @@ -0,0 +1,59 @@ +package templ_test + +import ( + "bytes" + "context" + "io" + "testing" + + "github.com/a-h/templ" + "github.com/google/go-cmp/cmp" +) + +func TestJoin(t *testing.T) { + hello := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { + if _, err := io.WriteString(w, "Hello"); err != nil { + t.Fatalf("failed to write string: %v", err) + } + return nil + }) + world := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { + if _, err := io.WriteString(w, "World"); err != nil { + t.Fatalf("failed to write string: %v", err) + } + return nil + }) + components := []templ.Component{hello, world} + emptyComponents := []templ.Component{} + + tests := []struct { + name string + input []templ.Component + expectedComponent string + }{ + { + name: "render hello world", + input: components, + expectedComponent: "HelloWorld", + }, + { + name: "pass an empty array", + input: emptyComponents, + expectedComponent: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := templ.Join(tt.input...) + b := new(bytes.Buffer) + err := got.Render(context.Background(), b) + if err != nil { + t.Fatalf("failure in rendering %s", err) + } + if diff := cmp.Diff(tt.expectedComponent, b.String()); diff != "" { + t.Error(diff) + } + }) + } +} From 2093b18ba371d3108b77ee3eb6e9692006138b96 Mon Sep 17 00:00:00 2001 From: Arman Date: Mon, 16 Sep 2024 22:45:55 +0100 Subject: [PATCH 2/7] docs: templ.Join example --- .../10-template-composition.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/docs/03-syntax-and-usage/10-template-composition.md b/docs/docs/03-syntax-and-usage/10-template-composition.md index e9d0f3b23..2362dfd6e 100644 --- a/docs/docs/03-syntax-and-usage/10-template-composition.md +++ b/docs/docs/03-syntax-and-usage/10-template-composition.md @@ -235,6 +235,62 @@ func main() {

Dynamic contents

``` +## Joining Components +With your templ components you can use `templ.Join` to generate a single `templ.Component` from your components. +```templ +package main + +import ( + "context" + "os" + + "github.com/a-h/templ" +) + +func main() { + hello := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { + _, err := io.WriteString(w, "
Hello
") + return err + }) + world := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { + _, err := io.WriteString(w, "
World
") + return err + }) + + actions := []templ.Component{hello, world} + + templ showAll() { + @Modal() { +
Inserted from the top
+ } + } + templ Modal(actions []templ.Component) { +
+
+ {...children} +
+
+ templ.Join(actions...) +
+
+ } +} +``` +```html title="output" +
+
+ Inserted from the top +
+
+
+
+ Hello +
+
+ World +
+
+``` ## Sharing and re-using components From bb8d3b9c520e77610ab9235ad0275fd8dbd47d38 Mon Sep 17 00:00:00 2001 From: Arman Date: Mon, 16 Sep 2024 23:17:09 +0100 Subject: [PATCH 3/7] docs: templ.Join example --- .../10-template-composition.md | 15 +-------------- join.go | 2 +- join_test.go | 2 +- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/docs/docs/03-syntax-and-usage/10-template-composition.md b/docs/docs/03-syntax-and-usage/10-template-composition.md index 2362dfd6e..1d7b65882 100644 --- a/docs/docs/03-syntax-and-usage/10-template-composition.md +++ b/docs/docs/03-syntax-and-usage/10-template-composition.md @@ -259,16 +259,8 @@ func main() { actions := []templ.Component{hello, world} - templ showAll() { - @Modal() { -
Inserted from the top
- } - } - templ Modal(actions []templ.Component) { + templ Modal(actions []templ.Component) {
-
- {...children} -
templ.Join(actions...)
@@ -277,11 +269,6 @@ func main() { } ``` ```html title="output" -
-
- Inserted from the top -
-
Hello diff --git a/join.go b/join.go index 525e4364f..2928d2aeb 100644 --- a/join.go +++ b/join.go @@ -5,7 +5,7 @@ import ( "io" ) -//Pass any number of templ.Components to get a single templ.Component with the components rendered +// Pass any number of templ.Components to get a single templ.Component with the components rendered func Join(components ...Component) Component { return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { for _, c := range components { diff --git a/join_test.go b/join_test.go index c98d67c6a..99d2907bb 100644 --- a/join_test.go +++ b/join_test.go @@ -24,7 +24,7 @@ func TestJoin(t *testing.T) { return nil }) components := []templ.Component{hello, world} - emptyComponents := []templ.Component{} + emptyComponents := []templ.Component{} tests := []struct { name string From 10b3adee2c87e3091796a01baf2f51697b68fa0c Mon Sep 17 00:00:00 2001 From: Arman Date: Sat, 28 Sep 2024 21:06:28 +0100 Subject: [PATCH 4/7] docs: updated descriptions and examples, added error case in test --- .../10-template-composition.md | 51 ++++++++----------- join_test.go | 25 ++++++--- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/docs/03-syntax-and-usage/10-template-composition.md b/docs/docs/03-syntax-and-usage/10-template-composition.md index 1d7b65882..3aea09975 100644 --- a/docs/docs/03-syntax-and-usage/10-template-composition.md +++ b/docs/docs/03-syntax-and-usage/10-template-composition.md @@ -236,47 +236,36 @@ func main() {
``` ## Joining Components -With your templ components you can use `templ.Join` to generate a single `templ.Component` from your components. +Components can be aggregated into a single Component using `templ.Join` ```templ package main -import ( - "context" - "os" +templ hello() { + hello +} - "github.com/a-h/templ" +templ world() { + world +} + +templ helloWorld() { + @templ.Join(hello(), world()) +} +``` +```go title="main.go" +package main + +import ( + "context" + "os" ) func main() { - hello := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { - _, err := io.WriteString(w, "
Hello
") - return err - }) - world := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { - _, err := io.WriteString(w, "
World
") - return err - }) - - actions := []templ.Component{hello, world} - - templ Modal(actions []templ.Component) { -
-
- templ.Join(actions...) -
-
- } + helloWorld().Render(context.Background(), os.Stdout) } ``` ```html title="output" -
-
- Hello -
-
- World -
-
+helloworld ``` ## Sharing and re-using components diff --git a/join_test.go b/join_test.go index 99d2907bb..583609ace 100644 --- a/join_test.go +++ b/join_test.go @@ -3,6 +3,7 @@ package templ_test import ( "bytes" "context" + "errors" "io" "testing" @@ -11,6 +12,8 @@ import ( ) func TestJoin(t *testing.T) { + compErr := errors.New("component error") + hello := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { if _, err := io.WriteString(w, "Hello"); err != nil { t.Fatalf("failed to write string: %v", err) @@ -23,23 +26,31 @@ func TestJoin(t *testing.T) { } return nil }) - components := []templ.Component{hello, world} - emptyComponents := []templ.Component{} + err := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { + return compErr + }) tests := []struct { name string input []templ.Component expectedComponent string + expectedErr error }{ { name: "render hello world", - input: components, + input: []templ.Component{hello, world}, expectedComponent: "HelloWorld", }, { name: "pass an empty array", - input: emptyComponents, + input: []templ.Component{}, + expectedComponent: "", + }, + { + name: "component returns an error", + input: []templ.Component{err}, expectedComponent: "", + expectedErr: compErr, }, } @@ -48,12 +59,12 @@ func TestJoin(t *testing.T) { got := templ.Join(tt.input...) b := new(bytes.Buffer) err := got.Render(context.Background(), b) - if err != nil { - t.Fatalf("failure in rendering %s", err) - } if diff := cmp.Diff(tt.expectedComponent, b.String()); diff != "" { t.Error(diff) } + if err != tt.expectedErr { + t.Fatalf("failure in rendering %s", err) + } }) } } From 5eb1ef6983af574ed18c14954b7d2ba13da2ad47 Mon Sep 17 00:00:00 2001 From: Arman Date: Sat, 28 Sep 2024 21:13:45 +0100 Subject: [PATCH 5/7] chore: fmt --- join_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/join_test.go b/join_test.go index 583609ace..1b1748bfe 100644 --- a/join_test.go +++ b/join_test.go @@ -50,7 +50,7 @@ func TestJoin(t *testing.T) { name: "component returns an error", input: []templ.Component{err}, expectedComponent: "", - expectedErr: compErr, + expectedErr: compErr, }, } From b6f623fb52400ff7918b1bce69155c54070997ef Mon Sep 17 00:00:00 2001 From: Arman Date: Sat, 28 Sep 2024 21:19:16 +0100 Subject: [PATCH 6/7] docs: updated join function comment --- join.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/join.go b/join.go index 2928d2aeb..a8093597a 100644 --- a/join.go +++ b/join.go @@ -5,7 +5,8 @@ import ( "io" ) -// Pass any number of templ.Components to get a single templ.Component with the components rendered +// Join returns a single `templ.Component` that will render provided components in order. +// If any of the components return an error the Join component will immediately return with the error. func Join(components ...Component) Component { return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { for _, c := range components { From 7efd27942d70d7c0a0c876ac91749ca947225966 Mon Sep 17 00:00:00 2001 From: Adrian Hesketh Date: Mon, 30 Sep 2024 11:57:50 +0100 Subject: [PATCH 7/7] refactor: minor changes to test names --- .../10-template-composition.md | 7 ++- join_test.go | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/docs/docs/03-syntax-and-usage/10-template-composition.md b/docs/docs/03-syntax-and-usage/10-template-composition.md index 3aea09975..6ce08fdd7 100644 --- a/docs/docs/03-syntax-and-usage/10-template-composition.md +++ b/docs/docs/03-syntax-and-usage/10-template-composition.md @@ -235,8 +235,11 @@ func main() {

Dynamic contents

``` + ## Joining Components -Components can be aggregated into a single Component using `templ.Join` + +Components can be aggregated into a single Component using `templ.Join`. + ```templ package main @@ -252,6 +255,7 @@ templ helloWorld() { @templ.Join(hello(), world()) } ``` + ```go title="main.go" package main @@ -264,6 +268,7 @@ func main() { helloWorld().Render(context.Background(), os.Stdout) } ``` + ```html title="output" helloworld ``` diff --git a/join_test.go b/join_test.go index 1b1748bfe..a0572f967 100644 --- a/join_test.go +++ b/join_test.go @@ -31,26 +31,37 @@ func TestJoin(t *testing.T) { }) tests := []struct { - name string - input []templ.Component - expectedComponent string - expectedErr error + name string + input []templ.Component + expectedOutput string + expectedErr error }{ { - name: "render hello world", - input: []templ.Component{hello, world}, - expectedComponent: "HelloWorld", + name: "a nil slice of components produces no output", + input: nil, + expectedOutput: "", }, { - name: "pass an empty array", - input: []templ.Component{}, - expectedComponent: "", + name: "an empty list of components produces no output", + input: []templ.Component{}, + expectedOutput: "", }, { - name: "component returns an error", - input: []templ.Component{err}, - expectedComponent: "", - expectedErr: compErr, + name: "components are rendered in order", + input: []templ.Component{hello, world}, + expectedOutput: "HelloWorld", + }, + { + name: "components are rendered in order, and errors returned", + input: []templ.Component{hello, err}, + expectedOutput: "Hello", + expectedErr: compErr, + }, + { + name: "no further components are rendered after an error", + input: []templ.Component{err, hello}, + expectedOutput: "", + expectedErr: compErr, }, } @@ -59,11 +70,11 @@ func TestJoin(t *testing.T) { got := templ.Join(tt.input...) b := new(bytes.Buffer) err := got.Render(context.Background(), b) - if diff := cmp.Diff(tt.expectedComponent, b.String()); diff != "" { - t.Error(diff) - } if err != tt.expectedErr { - t.Fatalf("failure in rendering %s", err) + t.Fatalf("failed to render component: %v", err) + } + if diff := cmp.Diff(tt.expectedOutput, b.String()); diff != "" { + t.Error(diff) } }) }