Handle language switching for every route
This commit is contained in:
+154
-14
@@ -3,10 +3,12 @@ package ersteller_lib
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"maragu.dev/gomponents"
|
||||
htmx "maragu.dev/gomponents-htmx"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HtmxContext interface {
|
||||
@@ -17,16 +19,74 @@ type HtmxContext interface {
|
||||
HasActivePath() bool
|
||||
SetActivePath(activePath *ActivePath)
|
||||
GetPath() string
|
||||
GetPathTemplate() string
|
||||
SetPathTemplate(pathTemplate string)
|
||||
GetPathParam(key string, defaultValue ...string) string
|
||||
GetFormValue(key string) string
|
||||
SetHeader(key string, value string)
|
||||
SetAllRoutes(routes []HtmxRoute)
|
||||
GetAllRoutes() []HtmxRoute
|
||||
GetQueryParams() []HtmxPathParam
|
||||
}
|
||||
|
||||
type HtmxContextImpl struct {
|
||||
req *http.Request
|
||||
res http.ResponseWriter
|
||||
language Language
|
||||
activePath *ActivePath
|
||||
req *http.Request
|
||||
res http.ResponseWriter
|
||||
language Language
|
||||
activePath *ActivePath
|
||||
pathTemplate string
|
||||
routes []HtmxRoute
|
||||
}
|
||||
|
||||
func (c *HtmxContextImpl) GetQueryParams() []HtmxPathParam {
|
||||
// Extract query parameters from the current request URL and return them as
|
||||
// a deterministic list of HtmxPathParam. If a key has multiple values,
|
||||
// each value is included as a separate entry with the same key. Keys are
|
||||
// returned in sorted order to keep behavior stable for tests and callers.
|
||||
if c == nil || c.req == nil || c.req.URL == nil {
|
||||
return []HtmxPathParam{}
|
||||
}
|
||||
|
||||
values := c.req.URL.Query() // url.Values already decoded
|
||||
if len(values) == 0 {
|
||||
return []HtmxPathParam{}
|
||||
}
|
||||
|
||||
// Collect and sort keys for deterministic output
|
||||
keys := make([]string, 0, len(values))
|
||||
for k := range values {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
params := make([]HtmxPathParam, 0)
|
||||
for _, k := range keys {
|
||||
vs := values[k]
|
||||
if len(vs) == 0 {
|
||||
params = append(params, HtmxPathParam{Key: k, Value: ""})
|
||||
continue
|
||||
}
|
||||
for _, v := range vs {
|
||||
params = append(params, HtmxPathParam{Key: k, Value: v})
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func (c *HtmxContextImpl) GetAllRoutes() []HtmxRoute {
|
||||
return c.routes
|
||||
}
|
||||
|
||||
func (c *HtmxContextImpl) SetAllRoutes(routes []HtmxRoute) {
|
||||
c.routes = routes
|
||||
}
|
||||
|
||||
func (c *HtmxContextImpl) SetPathTemplate(pathTemplate string) {
|
||||
c.pathTemplate = pathTemplate
|
||||
}
|
||||
|
||||
func (c *HtmxContextImpl) GetPathTemplate() string {
|
||||
return c.pathTemplate
|
||||
}
|
||||
|
||||
func (c *HtmxContextImpl) SetHeader(key string, value string) {
|
||||
@@ -110,11 +170,13 @@ type LocationRedirectParams struct {
|
||||
type HtmxRoute interface {
|
||||
WithPathParams(params ...HtmxPathParam) HtmxRoute
|
||||
ToUrl(language Language, queryParams ...HtmxPathParam) string
|
||||
ToUrlFromContext(c HtmxContext, language Language) string
|
||||
GetHtmx(language Language, queryParams ...HtmxPathParam) gomponents.Node
|
||||
SetActivePath(activePath *ActivePath) HtmxRoute
|
||||
Add(server *http.ServeMux)
|
||||
Add(server HtmxServer)
|
||||
Execute(c HtmxContext)
|
||||
RedirectToThisRoute(c HtmxContext, params LocationRedirectParams)
|
||||
IsCurrentRoute(c HtmxContext) bool
|
||||
}
|
||||
|
||||
func addLanguageToPath(path string, language Language) string {
|
||||
@@ -130,6 +192,18 @@ type CommonHtmxRoute struct {
|
||||
HtmxMethod HtmxHttpMethodFunction
|
||||
}
|
||||
|
||||
func (h CommonHtmxRoute) ToUrlFromContext(c HtmxContext, language Language) string {
|
||||
routeWithParams := h.WithPathParams(h.extractPathParams(c)...)
|
||||
queryParams := c.GetQueryParams()
|
||||
return routeWithParams.ToUrl(language, queryParams...)
|
||||
}
|
||||
|
||||
func (h CommonHtmxRoute) IsCurrentRoute(c HtmxContext) bool {
|
||||
templatePath := c.GetPathTemplate()
|
||||
urlOfThisRoute := h.ToUrl(c.GetLanguage())
|
||||
return templatePath == urlOfThisRoute
|
||||
}
|
||||
|
||||
func (h CommonHtmxRoute) RedirectToThisRoute(c HtmxContext, params LocationRedirectParams) {
|
||||
params.Path = h.ToUrl(c.GetLanguage())
|
||||
jsonData, err := json.Marshal(params)
|
||||
@@ -156,10 +230,12 @@ func (h CommonHtmxRoute) SetActivePath(activePath *ActivePath) HtmxRoute {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h CommonHtmxRoute) AddWithMethod(method string, server *http.ServeMux) {
|
||||
func (h CommonHtmxRoute) AddWithMethod(method string, server HtmxServer) {
|
||||
for language, path := range h.Paths {
|
||||
server.HandleFunc(method+" "+addLanguageToPath(path, language), func(res http.ResponseWriter, req *http.Request) {
|
||||
server.GetHttpServer().HandleFunc(method+" "+addLanguageToPath(path, language), func(res http.ResponseWriter, req *http.Request) {
|
||||
context := NewHtmxContext(req, res, language)
|
||||
context.SetPathTemplate(addLanguageToPath(path, language))
|
||||
context.SetAllRoutes(server.GetAllRoutes())
|
||||
if h.ActivePath != nil {
|
||||
context.SetActivePath(h.ActivePath)
|
||||
}
|
||||
@@ -168,7 +244,8 @@ func (h CommonHtmxRoute) AddWithMethod(method string, server *http.ServeMux) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h CommonHtmxRoute) Add(server *http.ServeMux) {
|
||||
func (h CommonHtmxRoute) Add(server HtmxServer) {
|
||||
server.Add(h)
|
||||
h.AddWithMethod(h.Method, server)
|
||||
}
|
||||
|
||||
@@ -188,6 +265,41 @@ func (h CommonHtmxRoute) ToUrl(language Language, queryParams ...HtmxPathParam)
|
||||
return path
|
||||
}
|
||||
|
||||
func (h CommonHtmxRoute) extractPathParams(c HtmxContext) []HtmxPathParam {
|
||||
langPath := addLanguageToPath(h.Paths[c.GetLanguage()], c.GetLanguage())
|
||||
|
||||
// Extract all placeholders of the form {variable} from the language-specific path template
|
||||
params := make([]HtmxPathParam, 0)
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for {
|
||||
start := strings.Index(langPath, "{")
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
end := strings.Index(langPath[start+1:], "}")
|
||||
if end == -1 {
|
||||
break
|
||||
}
|
||||
end = start + 1 + end
|
||||
|
||||
key := strings.TrimSpace(langPath[start+1 : end])
|
||||
if key != "" && !seen[key] {
|
||||
seen[key] = true
|
||||
value := c.GetPathParam(key)
|
||||
params = append(params, HtmxPathParam{Key: key, Value: value})
|
||||
}
|
||||
|
||||
// Continue searching after current closing brace
|
||||
if end+1 >= len(langPath) {
|
||||
break
|
||||
}
|
||||
langPath = langPath[end+1:]
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
type HtmxHttpMethodFunction func(url string) gomponents.Node
|
||||
|
||||
func (h CommonHtmxRoute) GetHtmxFromMethod(method HtmxHttpMethodFunction, language Language, queryParams ...HtmxPathParam) gomponents.Node {
|
||||
@@ -206,10 +318,38 @@ func NewHtmxPostRoute(routeFunc HtmxRouteFunc, paths LanguagePaths) HtmxRoute {
|
||||
return NewCommonHtmxRoute(routeFunc, paths, "POST", htmx.Post)
|
||||
}
|
||||
|
||||
func HandleStaticAndDefaultPath(server *http.ServeMux, defaultRoute HtmxRoute, defaultLanguage Language) func(http.ResponseWriter, *http.Request) {
|
||||
type HtmxServer interface {
|
||||
Add(route HtmxRoute)
|
||||
GetAllRoutes() []HtmxRoute
|
||||
GetHttpServer() *http.ServeMux
|
||||
HandleStaticAndDefaultPath(defaultRoute HtmxRoute, defaultLanguage Language)
|
||||
}
|
||||
|
||||
type HtmxServerImpl struct {
|
||||
routes []HtmxRoute
|
||||
server *http.ServeMux
|
||||
}
|
||||
|
||||
func (h *HtmxServerImpl) GetAllRoutes() []HtmxRoute {
|
||||
return h.routes
|
||||
}
|
||||
|
||||
func NewHtmxServer() *HtmxServerImpl {
|
||||
return &HtmxServerImpl{server: http.NewServeMux()}
|
||||
}
|
||||
|
||||
func (h *HtmxServerImpl) Add(route HtmxRoute) {
|
||||
h.routes = append(h.routes, route)
|
||||
}
|
||||
|
||||
func (h *HtmxServerImpl) GetHttpServer() *http.ServeMux {
|
||||
return h.server
|
||||
}
|
||||
|
||||
func (h *HtmxServerImpl) HandleStaticAndDefaultPath(defaultRoute HtmxRoute, defaultLanguage Language) {
|
||||
staticHandler := http.StripPrefix("/static", http.FileServer(http.Dir("./static")))
|
||||
server.Handle("GET /static", staticHandler)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.server.Handle("GET /static", staticHandler)
|
||||
h.server.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if strings.HasPrefix(path, "/static") {
|
||||
staticHandler.ServeHTTP(w, r)
|
||||
@@ -220,5 +360,5 @@ func HandleStaticAndDefaultPath(server *http.ServeMux, defaultRoute HtmxRoute, d
|
||||
return
|
||||
}
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user