How you can work?
Golang front-end workflow
The HTMX-based approach can be extremely effective, but requires a well-organized workflow and an understanding of the concept of how an API returns rendered views. The steps in this process might look like this:
Understanding data
It is crucial to understand what data structure we will display in the presentation layer, what the logic and dependencies are in the operations performed on the data. We define types and routing.
1// Let's define struct
2
3type Article struct {
4 Id string
5 Title string
6 Tags []config.Tag
7 Post string
8 Active bool
9}
Planning views with states
We plan what should be included in views with specific states (for example, form before submission and validated). We assign it to routing and display the raw data.
1// If you want post form
2
3article := &Article{
4 Title: r.Form.Get("title"),
5 Content: r.Form.Get("content"),
6}
7
8validations.Errors = article.validate()
9templ.Handler(template.NewArticleForm(validations)).ServeHTTP(w, r)
Planning the component list
We should break each view into smaller fragments - components. We define the scope of each component according to the extended atomic design approach, obtaining: protons, atoms, molecules and organisms.
1// How component look like
2
3@atoms.FormField(&atoms.FormFieldBosons{}) {
4 @atoms.FormLabel(&atoms.FormLabelBosons{Text: "Email:"})
5 @atoms.FormInputField(&atoms.FormInputFieldBosons{
6 Error: valid.IsError("Email"),
7 }) {
8 @atoms.FormInput(&atoms.FormInputBosons{
9 Size: "lg",
10 Attrs: gopress.Attrs{"name": "email", "id": "email", "value": valid.GetValue("Email")},
11 })
12}
Templ and Tailwind in components
We create components knowing what responsibility and role they have. Each component may contain bosons - sets of variables that format and manage the state of the component.
1// Component and Tailwind bosons
2
3type SomeComponentBosons struct {
4 Color string
5}
6var someComponentColorStyles = gopress.Boson{
7 "primary": "bg-primary-600",
8 "secondary": "bg-secondary-600",
9}
Adding Alpine.js
We add directives from the Alpine.js library to the components. We add official plugins or create our own. To make the result code semantically correct, we use the "data-" prefix.
1// Building Alpine
2
3import Alpine from 'alpinejs'
4import persist from '@alpinejs/persist'
5import focus from '@alpinejs/focus'
6Alpine.plugin(focus)
7Alpine.plugin(persist)
8Alpine.prefix('data-x-')
Composing templates
Component create templates. Templates place components within a layout and demonstrate the design's underlying content structure.
1// Sample layout
2
3@Layout() {
4 @partials.Head()
5 @partials.Topbar()
6 { children... }
7 @partials.Footer()
8}
Integration with API and data
Any template or component can be connected to the API endpoint. Templates in the Atomic Design approach display mocked data (dev environment), and "pages" display production data.
1// Making endpoints
2
3http.HandleFunc("/", getTemplate)
4http.HandleFunc("/component/{group}/{component}", getComponent)
5http.HandleFunc("/form", getForm)
6http.HandleFunc("/partial/{component}", getPartial)
7http.ListenAndServe(":"+os.Getenv("APP_PORT"), nil)
Adding HTMX
Using HTMX directives, we replace the template state, i.e. a group of components with specific data under a defined routing, with a group of components expected at a different endpoint.
1// Adding HTMX
2
3<button
4 data-hx-get={ "/partial/" + component }
5 data-hx-target="#content"
6 data-hx-swap="innerHTML"
7 hx-push-url={ url }
8 title={ title }
9>{ title }</button>