|
| 1 | +# Fragments |
| 2 | + |
| 3 | +The `templ.Fragment` component can be used to render a subsection of a template, discarding all other output. |
| 4 | + |
| 5 | +Fragments work well as an optimisation for HTMX, as discussed in https://htmx.org/essays/template-fragments/ |
| 6 | + |
| 7 | +## Define fragments |
| 8 | + |
| 9 | +Define a fragment with `@templ.Fragment("name")`, where `"name"` is the identifier for the fragment. |
| 10 | + |
| 11 | +```templ |
| 12 | +templ Page() { |
| 13 | + <div>Page Header</div> |
| 14 | + @templ.Fragment("name") { |
| 15 | + <div>Content of the fragment</div> |
| 16 | + } |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +To avoid name clashes with other libraries, you can define a custom type for your package. |
| 21 | + |
| 22 | +```templ |
| 23 | +type nameFragmentKey struct {} |
| 24 | +var Name = nameFragmentKey{} |
| 25 | +
|
| 26 | +templ Page() { |
| 27 | + <div>Page Header</div> |
| 28 | + @templ.Fragment(Name) { |
| 29 | + <div>Content of the fragment</div> |
| 30 | + } |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +## Use with HTTP |
| 35 | + |
| 36 | +The most common use case for `Fragment` is to render only a specific part of the template to the HTML response, while discarding the rest of the output. |
| 37 | + |
| 38 | +To render only the "name" fragment from the `Page` template, use the `templ.WithFragments("name")` option when creating the HTTP handler: |
| 39 | + |
| 40 | +```go title="main.go" |
| 41 | +handler := templ.Handler(Page(), templ.WithFragments("name")) |
| 42 | +http.Handle("/", handler) |
| 43 | +``` |
| 44 | + |
| 45 | +When the HTTP request is made, only the content of the specified fragment will be returned in the response: |
| 46 | + |
| 47 | +```html title="output.html" |
| 48 | +<div>Content of the fragment</div> |
| 49 | +``` |
| 50 | + |
| 51 | +:::note |
| 52 | +The whole of the template is rendered, so any function calls or logic in the template will still be executed, but only the specified fragment's output is sent to the client. |
| 53 | +::: |
| 54 | + |
| 55 | +If the `templ.WithFragments("name")` option is omitted, the whole page is rendered as normal. |
| 56 | + |
| 57 | +```go title="main.go" |
| 58 | +handler := templ.Handler(Page()) |
| 59 | +http.Handle("/", handler) |
| 60 | +``` |
| 61 | + |
| 62 | +```html title="output.html" |
| 63 | +<div>Page Header</div> |
| 64 | +<div>Content of the fragment</div> |
| 65 | +``` |
| 66 | + |
| 67 | +## Use outside of an HTTP handler |
| 68 | + |
| 69 | +To use outside of an HTTP handler, e.g. when generating static content, you can render fragments with the `templ.RenderFragments` function. |
| 70 | + |
| 71 | +```go |
| 72 | +w := new(bytes.Buffer) |
| 73 | +if err := templ.RenderFragments(context.Background(), w, fragmentPage, "name"); err != nil { |
| 74 | + t.Fatalf("failed to render: %v", err) |
| 75 | +} |
| 76 | + |
| 77 | +// <div>Content of the fragment</div> |
| 78 | +html := w.String() |
| 79 | +``` |
| 80 | + |
| 81 | +:::note |
| 82 | +All fragments with matching identifiers will be rendered. If the fragment identifier isn't matched, no output will be produced. |
| 83 | +::: |
| 84 | + |
| 85 | +## Nested fragments |
| 86 | + |
| 87 | +Fragments can be nested, allowing for complex structures to be defined and rendered selectively. |
| 88 | + |
| 89 | +Given this example templ file: |
| 90 | + |
| 91 | +```templ |
| 92 | +templ Page() { |
| 93 | + @templ.Fragment("outer") { |
| 94 | + <div>Outer Fragment Start</div> |
| 95 | + @templ.Fragment("inner") { |
| 96 | + <div>Inner Fragment Content</div> |
| 97 | + } |
| 98 | + <div>Outer Fragment End</div> |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +If the `outer` fragment is selected for rendering, then the `inner` fragment is also rendered. |
| 104 | + |
| 105 | +## HTMX example |
| 106 | + |
| 107 | +```templ title="main.templ" |
| 108 | +package main |
| 109 | +
|
| 110 | +import ( |
| 111 | + "fmt" |
| 112 | + "net/http" |
| 113 | + "strconv" |
| 114 | +) |
| 115 | +
|
| 116 | +type PageState struct { |
| 117 | + Counter int |
| 118 | + Next int |
| 119 | +} |
| 120 | +
|
| 121 | +templ Page(state PageState) { |
| 122 | + <html> |
| 123 | + <head> |
| 124 | + <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js"></script> |
| 125 | + <link rel="stylesheet" href="https://unpkg.com/missing.css@1.1.3/dist/missing.min.css"/> |
| 126 | + </head> |
| 127 | + <body> |
| 128 | + @templ.Fragment("buttonOnly") { |
| 129 | + <button hx-get={ fmt.Sprintf("/?counter=%d&template=buttonOnly", state.Next) } hx-swap="outerHTML"> |
| 130 | + This Button Has Been Clicked { state.Counter } Times |
| 131 | + </button> |
| 132 | + } |
| 133 | + </body> |
| 134 | + </html> |
| 135 | +} |
| 136 | +
|
| 137 | +// handleRequest does the work to execute the template (or fragment) and serve the result. |
| 138 | +// It's mostly boilerplate, so don't get hung up on it. |
| 139 | +func handleRequest(w http.ResponseWriter, r *http.Request) { |
| 140 | + // Collect state info to pass to the template. |
| 141 | + var state PageState |
| 142 | + state.Counter, _ = strconv.Atoi(r.URL.Query().Get("counter")) |
| 143 | + state.Next = state.Counter + 1 |
| 144 | +
|
| 145 | + // If the template querystring paramater is set, render the pecific fragment. |
| 146 | + var opts []func(*templ.ComponentHandler) |
| 147 | + if templateName := r.URL.Query().Get("template"); templateName != "" { |
| 148 | + opts = append(opts, templ.WithFragments(templateName)) |
| 149 | + } |
| 150 | +
|
| 151 | + // Render the template or fragment and serve it. |
| 152 | + templ.Handler(Page(state), opts...).ServeHTTP(w, r) |
| 153 | +} |
| 154 | +
|
| 155 | +func main() { |
| 156 | + // Handle the template. |
| 157 | + http.HandleFunc("/", handleRequest) |
| 158 | + |
| 159 | + // Start the server. |
| 160 | + fmt.Println("Server is running at http://localhost:8080") |
| 161 | + http.ListenAndServe("localhost:8080", nil) |
| 162 | +} |
| 163 | +``` |
| 164 | + |
| 165 | +:::note |
| 166 | +This was adapted from `benpate`'s Go stdlib example at https://gist.github.com/benpate/f92b77ea9b3a8503541eb4b9eb515d8a |
| 167 | +::: |
0 commit comments