/gromer

gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.

Primary LanguageJavaScript

gromer

Version

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.

Requirements

go >= v1.18

Install

go get -u -v github.com/pyros2097/gromer/cmd/gromer

Using

You need to follow this directory structure similar to nextjs for the api route handlers to be generated and run the gromer command.

Example

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")
	}
}

TODO:

Add inline css formatting

Add inline html formatting