v0.5.7 beta
Back-end UI Framework

for feature-rich, secure, and fast web apps in Go

Tutorial

Fragment And Styles

Using functional components (individual templ functions) is fine, but in many cases, it’s easier to deal with structured data.

1. Category Data

Our current category functional component:

templ category() {
	<h1>Category</h1>
	// href to go test category page
	@doors.AHref{
		Model: Path{
			IsMain: true,
		},
	}
	<a>Go back</a>
}

To query category data from the DB, we need the category ID. Obtain the category ID from the path beam, add argument to the category component:

templ category(path doors.SourceBeam[Path]) 

Provide the path beam:

./catalog/page.templ

/* ... */
func (c *catalogPage) Body() templ.Component {
	b := doors.NewBeam(c.path, func(p Path) bool {
		return p.IsMain
	})
	return doors.Sub(b, func(isMain bool) templ.Component {
		if isMain {
			return main()
		}
		// add path beam dependency
		return category(c.path)
	})
}
/* ... */

Next, derive the category ID beam and subscribe to it:

./catalog/category.templ

templ category(path doors.SourceBeam[Path]) {
  // subscribe to the derived beam
	@doors.Sub(doors.NewBeam(path, func(p Path) string {
	  // derive category id
		return p.CatId
	}), func(catId string) templ.Component {
	  // render new category when when ID changes
		return categoryContent(catId)
	})
	@doors.AHref{
		Model: Path{
			IsMain: true,
		},
	}
	<a>Go back</a>
}

templ categoryContent(catId string) {
  // query category
	{{ cat, ok := driver.Cats.Get(catId) }}
	// category found
	if ok {
		<hgroup>
			<h1>{ cat.Name }</h1>
			<p>{ cat.Desc } </p>
		</hgroup>
	} else {
	  // set status code (for the initial load)
		@doors.Status(http.StatusNotFound)
		<div>
			<h1>Not Found</h1>
		</div>
	}
}

2. Refactor to Fragment

Instead of a templ function, we can have a struct type, which will allows to group all pieces nicely in its methods/fields.

type categoryFragment struct {
	path doors.SourceBeam[Path]
}

// doors.Fragment must have Render() method
templ (f *categoryFragment) Render() {
	@doors.Sub(doors.NewBeam(f.path, func(p Path) string {
		return p.CatId
	}), func(catId string) templ.Component {
		return f.content(catId)
	})
	@doors.AHref{
		Model: Path{
			IsMain: true,
		},
	}
	<a class="contrast">Go back</a>
}

templ (f *categoryFragment) content(catId string) {
	{{ cat, ok := driver.Cats.Get(catId) }}
	if ok {
		<hgroup>
			<h1>{ cat.Name }</h1>
			<p>{ cat.Desc } </p>
		</hgroup>
	} else {
		@doors.Status(http.StatusNotFound)
		<div>
			<h1>Not Found</h1>
		</div>
	}
}

To keep it compatible with the previous implementation, wrap fragment in a component:

func category(path doors.SourceBeam[Path]) templ.Component {
	// doors.F - fragment renderer, also can be used in templates @doors.F(doors.Fragment)
	return doors.F(&categoryFragment{
		path: path,
	})
}

3. Layout and Styles

Styles

Prepare styles for layout with side nav.

templ (f *categoryFragment) style() {
	// converts inline style to cachable stylesheet with href
	@doors.Style() {
        <style>
            .category {
                display: flex;
                gap: calc(var(--pico-typography-spacing-vertical) * 3);
            }
            aside {
                flex: 0 0 auto; 
            }
            main {
                flex: 1;
            }
        </style>
	}
}

Nav Component

templ (d *categoryFragment) nav() {
	<nav>
		<ul>
			<li>
				// link to main category variant
				@doors.AHref{
					Model: Path{
						IsMain: true,
					},
				}
				<a class="secondary">Go back</a>
			</li>
			// query categories
			for _, cat := range driver.Cats.List() {
				<li>
					// attach href 
					@doors.AHref{
						// link to category
						Model: Path{
							IsCat: true,
							CatId: cat.Id,
						},
						Active: doors.Active{
							// indicate with attribute
							Indicator: doors.IndicatorOnlyAttr("aria-current", "page"),
							// path must start with href
							PathMatcher: doors.PathMatcherStarts(),
							// ignore query params
							QueryMatcher: doors.QueryMatcherIgnore(),
						},
					}
					<a class="contrast">{ cat.Name }</a>
				</li>
			}
		</ul>
	</nav>
}

Layout

Assemble all tougher in fragment render function

templ (f *categoryFragment) Render() {
	@f.style()
	<div class="category">
		<aside>
			@f.nav()
		</aside>
		<div class="content">
			@doors.Sub(doors.NewBeam(f.path, func(p Path) string {
				return p.CatId
			}), func(catId string) templ.Component {
				return f.content(catId)
			})
		</div>
	</div>
}

Conclusion

By this time, you have learned one of the core frameworks’ mechanics: Beam derivation and subscription. As you probably already understand, the framework establishes an explicit and direct connection between data and HTML updates, without relying on hidden virtual DOM diffs (diff data, not DOM).

In the following chapters, we will cover authentication and event handling.


Next: Form Auth