Catalog Page
Mini app with three pages.
- Show a list of categories.
- Show a list of items within a category.
- Show item card.
I assume you have live reloading enabled. If you prefer not to use it, don’t forget to run templ generate and restart.
1. Path Model
Let’s add a new page path model.
./common/path.go
package common
type CatalogPath struct {
IsMain bool `path:"/catalog"` // show categories
IsCat bool `path:"/catalog/:CatId"` // show items of category
IsItem bool `path:"/catalog/:CatId/:ItemId"` // show item
CatId string
ItemId int
Page *int `query:"page"` // query param for pagination (used pointer to avoid showing 0 default value)
}
// prev one, keep it
type HomePath struct {
Main bool `path:"/"`
}
2. Page & Handler
In the new catalog
package
./catalog/page.templ
package catalog
import "github.com/doors-dev/doors"
import "github.com/derstruct/doors-tutorial/common"
type Path = common.CatalogPath
type catalogPage struct {
}
templ (c *catalogPage) Head() {
<title>catalog</title>
}
templ (c *catalogPage) Body() {
<h1>Catalog</h1>
}
/*
Instead of doing this:
templ (c *catalogPage) Render(b doors.SourceBeam[Path]) {
@common.Template(c)
}
Because there is only one component `common.Template(c)`, we can return it directly:
*/
func (c *catalogPage) Render(b doors.SourceBeam[Path]) templ.Component {
return common.Template(c)
}
// page request handler
func Handler(p doors.PageRouter[Path], r doors.RPage[Path]) doors.PageRoute {
return p.Page(&catalogPage{})
}
3. Router
./main.go
package main
import (
"net/http"
"github.com/derstruct/doors-tutorial/catalog"
"github.com/derstruct/doors-tutorial/home"
"github.com/doors-dev/doors"
)
func main() {
r := doors.NewRouter()
r.Use(
doors.UsePage(home.Handler),
// our new catalog page
doors.UsePage(catalog.Handler),
)
err := http.ListenAndServeTLS(":8443", "localhost+2.pem", "localhost+2-key.pem", r)
if err != nil {
panic(err)
}
}
4. Check
Visit https://localhost:8443/catalog
5. Styling
To save some time, let’s use PicoCSS’s default styles. Include CDN styles in our template.
./common/page.templ
package common
import "github.com/doors-dev/doors"
type Page interface {
Head() templ.Component
Body() templ.Component
}
templ Template(p Page) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="color-scheme" content="light dark"/>
@doors.Include()
// same as <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"/>
// but adds CSP headers (useful if you enable CSP later)
@doors.ImportStyleExternal{
Href: "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css",
}
@p.Head()
</head>
<body>
@p.Body()
</body>
</html>
}
Please refer to Imports for details
5. Menu
We now have two pages; let’s add some navigation.
./common/components.templ
package common
templ menu() {
<nav>
<ul>
<li><strong>doors tutorial</strong></li>
</ul>
<ul>
<li>
<a href="/">home</a>
</li>
<li>
<a href="/catalog">services</a>
</li>
</ul>
</nav>
}
Include it in our template.
./common/page.templ
templ Template(p Page) {
/* ... */
<body>
// PicoCSS container
<main class="container">
// menu component
@menu()
@p.Body()
</main>
</body>
/* ... */
}
Confirm it works in the browser
5. Idiomatic Menu
doors allows you to create href attributes in a type-safe manner and includes extensive tools for highlighting active links.
/common/components.templ
package common
import "github.com/doors-dev/doors"
templ menu() {
<nav>
<ul>
<li><strong>doors tutorial</strong></li>
</ul>
<ul>
<li>
// Magic attribute syntax, attribute attached to the next element
@doors.AHref{
// href Path Model
Model: HomePath{},
// active link highlighting settings
Active: doors.Active{
// indicate active link with attribute aria-current="page"
Indicator: doors.IndicatorOnlyAttr("aria-current", "page"),
},
}
<a>home</a>
</li>
<li>
// Magic attribute syntax, attribute attached to the next element
@doors.AHref{
// href Path Model
Model: CatalogPath{
// marker of variant
IsMain: true,
},
// active link highlighting settings
Active: doors.Active{
// indicate active link with attribute aria-current="page"
Indicator: doors.IndicatorOnlyAttr("aria-current", "page"),
// page path must start with href value to enable highlighting
PathMatcher: doors.PathMatcherStarts(),
// ignore query params
QueryMatcher: doors.QueryMatcherIgnore(),
},
}
<a>services</a>
</li>
</ul>
</nav>
}
Navigation between the home page and the catalog triggers a new page load because you’re switching between different path models. We learn about dynamic pages in the next article.
Please refer to Href and Src for full details on href
. Alternatively, instead of using Magic Attribute syntax, you can use attribute spread; see Attributes for details.
6. Conclusion
Visit https://localhost:8443/ to verify that page switching works and active link indication is applied.
Next: Dynamic Content