gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
It uses a declarative syntax using inline jsx like templates for components and pages.
It also generates http handlers for your routes which follow a particular folder structure. Similar to other frameworks like nextjs, sveltekit.
You can install this extension vscode-go-inline-html to get syntax highlighting for these templates.
go >= v1.18
go get -u -v github.com/pyros2097/gromer/cmd/gromer
You need to follow this directory structure similar to nextjs for the api route handlers to be generated and run the gromer command.
These are some components
routes/todo.go
func Todo(c Context, todo *todos.Todo) *Node {
return c.Render(`
<li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
<div class="view">
<form hx-target="#todo-{todo.ID}" hx-swap="outerHTML">
<input type="hidden" name="intent" value="complete" />
<input type="hidden" name="id" value="{todo.ID}" />
<input class="checkbox" type="checkbox" checked="{value}" />
</form>
<label>{todo.Text}</label>
<form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete">
<input type="hidden" name="intent" value="delete" />
<input type="hidden" name="id" value="{todo.ID}" />
<button class="destroy"></button>
</form>
</div>
</li>
`)
}
These are normal page routes
routes/get.go
type GetParams struct {
Page int `json:"page"`
Filter string `json:"filter"`
}
func GET(c Context, params GetParams) (*Node, int, error) {
c.Meta("title", "Gromer Todos")
c.Meta("description", "Gromer Todos")
c.Meta("author", "gromer")
c.Meta("keywords", "gromer")
return c.Render(`
<div class="todoapp">
<header class="header">
<h1>todos</h1>
<form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
<input type="hidden" name="intent" value="create" />
<input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" />
</form>
</header>
<section class="main">
<input class="toggle-all" id="toggle-all" type="checkbox" />
<label for="toggle-all">Mark all as complete</label>
<TodoList id="todo-list" page="{params.Page}" filter="{params.Filter}" />
</section>
<footer class="footer">
<TodoCount filter="{params.Filter}" />
<ul class="filters">
<li>
<a href="?filter=all">All</a>
</li>
<li>
<a href="?filter=active">Active</a>
</li>
<li>
<a href="?filter=completed">Completed</a>
</li>
</ul>
<form hx-target="#todo-list" hx-post="/">
<input type="hidden" name="intent" value="clear_completed" />
<button type="submit" class="clear-completed" >Clear completed</button>
</form>
</footer>
</div>
`), 200, nil
}
And then run the gromer cli command annd it will generate the route handlers in a main.go file,
main.go
// Code generated by gromer. DO NOT EDIT.
package main
import (
"github.com/gorilla/mux"
"github.com/pyros2097/gromer"
"github.com/pyros2097/gromer/assets"
"github.com/pyros2097/gromer/gsx"
"github.com/rs/zerolog/log"
"gocloud.dev/server"
"github.com/pyros2097/gromer/_example/assets"
"github.com/pyros2097/gromer/_example/components"
"github.com/pyros2097/gromer/_example/containers"
"github.com/pyros2097/gromer/_example/routes/404"
"github.com/pyros2097/gromer/_example/routes"
"github.com/pyros2097/gromer/_example/routes/about"
)
func init() {
gsx.RegisterComponent(components.Todo, "todo")
gsx.RegisterComponent(components.Checkbox, "value")
gsx.RegisterComponent(containers.TodoCount, "filter")
gsx.RegisterComponent(containers.TodoList, "page", "filter")
}
func main() {
baseRouter := mux.NewRouter()
baseRouter.Use(gromer.LogMiddleware)
baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
staticRouter := baseRouter.NewRoute().Subrouter()
staticRouter.Use(gromer.CacheMiddleware)
gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS)
gromer.StaticRoute(staticRouter, "/assets/", assets.FS)
gromer.StylesRoute(staticRouter, "/styles.css")
pageRouter := baseRouter.NewRoute().Subrouter()
gromer.Handle(pageRouter, "GET", "/", routes.GET)
gromer.Handle(pageRouter, "POST", "/", routes.POST)
gromer.Handle(pageRouter, "GET", "/about", about.GET)
log.Info().Msg("http server listening on http://localhost:3000")
srv := server.New(baseRouter, nil)
if err := srv.ListenAndServe(":3000"); err != nil {
log.Fatal().Stack().Err(err).Msg("failed to listen")
}
}
Add inline css formatting
Add inline html formatting