diff --git a/datastar/go.mod b/datastar/go.mod new file mode 100644 index 0000000..68e23d8 --- /dev/null +++ b/datastar/go.mod @@ -0,0 +1,15 @@ +module datastar + +go 1.25.2 + +require ( + github.com/starfederation/datastar-go v1.0.3 + github.com/zepyrshut/hrender v0.0.0-20251208222655-1e403e42aac7 +) + +require ( + github.com/CAFxX/httpcompression v0.0.9 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect +) diff --git a/datastar/go.sum b/datastar/go.sum new file mode 100644 index 0000000..33e7676 --- /dev/null +++ b/datastar/go.sum @@ -0,0 +1,35 @@ +github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg= +github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/starfederation/datastar-go v1.0.3 h1:DnzgsJ6tDHDM6y5Nxsk0AGW/m8SyKch2vQg3P1xGTcU= +github.com/starfederation/datastar-go v1.0.3/go.mod h1:stm83LQkhZkwa5GzzdPEN6dLuu8FVwxIv0w1DYkbD3w= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= +github.com/zepyrshut/hrender v0.0.0-20251204134824-5eb5dc8eaf21 h1:jUfJj+Ymdd9krHUt3YzBxR5kFoRAp+RD3u4Yir/KSGQ= +github.com/zepyrshut/hrender v0.0.0-20251204134824-5eb5dc8eaf21/go.mod h1:KxR0Cisj52sFFxMTm3o+OJWBqP/khUpA1bjjc49iAiM= +github.com/zepyrshut/hrender v0.0.0-20251204145920-50fdd9cb5ff1 h1:Zpay8/pWw++3B/QXsGbF3eTI1z2tGynguQWAnERIg9c= +github.com/zepyrshut/hrender v0.0.0-20251204145920-50fdd9cb5ff1/go.mod h1:KxR0Cisj52sFFxMTm3o+OJWBqP/khUpA1bjjc49iAiM= +github.com/zepyrshut/hrender v0.0.0-20251208222655-1e403e42aac7 h1:qVpI47wId0u0dTMk8QHGaiml2vWzDjsFgCDr0JnyOCg= +github.com/zepyrshut/hrender v0.0.0-20251208222655-1e403e42aac7/go.mod h1:KxR0Cisj52sFFxMTm3o+OJWBqP/khUpA1bjjc49iAiM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/datastar/main.go b/datastar/main.go new file mode 100644 index 0000000..4d1bc62 --- /dev/null +++ b/datastar/main.go @@ -0,0 +1,176 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + "sort" + "strconv" + + "github.com/starfederation/datastar-go/datastar" + "github.com/zepyrshut/hrender" +) + +type TodoList struct { + ID int + Name string + Completed bool +} + +var todoList = []TodoList{ + { + ID: 1, + Name: "Sacar a la perra", + Completed: false, + }, + { + ID: 2, + Name: "Limpiar la casa", + Completed: false, + }, + { + ID: 3, + Name: "Ir al supermercado", + Completed: false, + }, +} + +type TodoSignals struct { + NewTodoName string `json:"newtodo"` +} + +func main() { + templatesFS := os.DirFS("./templates") + + h := hrender.NewHTMLRender(templatesFS, false) + + mux := http.NewServeMux() + mux.Handle("GET /", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(hrender.ContentType, hrender.ContentTextHTMLUTF8) + err := h.RenderW(w, "pages/index", hrender.H{}) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + })) + + mux.Handle("GET /todo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + sort.Slice(todoList, func(i, j int) bool { + return todoList[i].ID > todoList[j].ID + }) + + templ, err := h.RenderS("fragments/todo-widget", hrender.H{ + "TodoList": todoList, + }) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + + sse := datastar.NewSSE(w, r) + if err := sse.PatchElements(templ); err != nil { + log.Println("error", err) + } + })) + + mux.Handle("GET /todo/new", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + templ, err := h.RenderS("fragments/todo-row-edit", hrender.H{}) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + + sse := datastar.NewSSE(w, r) + if err := sse.PatchElements(templ, + datastar.WithModePrepend(), + datastar.WithSelectorID("todo-list-body"), + ); err != nil { + log.Println("error", err) + } + })) + + mux.Handle("POST /todo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var signals TodoSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + newID := 0 + sort.Slice(todoList, func(i, j int) bool { + return todoList[i].ID > todoList[j].ID + }) + newID = todoList[0].ID + 1 + + newItem := TodoList{ + ID: newID, + Name: signals.NewTodoName, + Completed: false, + } + + todoList = append(todoList, newItem) + + templ, _ := h.RenderS("fragments/todo-row", hrender.H{ + "ID": newID, + "Name": newItem.Name, + "Completed": false, + }) + + sse := datastar.NewSSE(w, r) + + if err := sse.PatchElements(templ, + datastar.WithModeReplace(), + datastar.WithSelectorID("new-todo-row"), + ); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + signals.NewTodoName = "" + sse.MarshalAndPatchSignals(signals) + })) + + mux.Handle("PATCH /todo/{id}/completed", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println("patch todo triggered") + + idStr := r.PathValue("id") + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "invalid id", http.StatusBadRequest) + return + } + + var foundItem *TodoList + for i := range todoList { + if todoList[i].ID == id { + todoList[i].Completed = true + foundItem = &todoList[i] + break + } + } + + if foundItem == nil { + http.Error(w, "item not found", http.StatusNotFound) + return + } + + templ, err := h.RenderS("fragments/todo-row", hrender.H{ + "ID": foundItem.ID, + "Name": foundItem.Name, + "Completed": true, + }) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + + sse := datastar.NewSSE(w, r) + if err := sse.PatchElements(templ, datastar.WithModeOuter(), + datastar.WithSelectorID(fmt.Sprintf("todo-%d", id)), + ); err != nil { + http.Error(w, "error", http.StatusInternalServerError) + } + })) + + log.Println("server started on port 8080") + err := http.ListenAndServe(":8080", mux) + if err != nil { + panic(fmt.Sprintf("server cannot start %v", err)) + } +} diff --git a/datastar/templates/fragments/todo-row-edit.html b/datastar/templates/fragments/todo-row-edit.html new file mode 100644 index 0000000..c4a06ba --- /dev/null +++ b/datastar/templates/fragments/todo-row-edit.html @@ -0,0 +1,12 @@ +
  • +
    + + +
    +
  • diff --git a/datastar/templates/fragments/todo-row.html b/datastar/templates/fragments/todo-row.html new file mode 100644 index 0000000..855dc3e --- /dev/null +++ b/datastar/templates/fragments/todo-row.html @@ -0,0 +1,15 @@ +
  • + +
  • diff --git a/datastar/templates/fragments/todo-widget.html b/datastar/templates/fragments/todo-widget.html new file mode 100644 index 0000000..1b373da --- /dev/null +++ b/datastar/templates/fragments/todo-widget.html @@ -0,0 +1,9 @@ +
    +

    Listado de tareas

    + + + +
      + {{ range .TodoList }} {{ template "fragments/todo-row" . }} {{ end }} +
    +
    diff --git a/datastar/templates/pages/index.html b/datastar/templates/pages/index.html new file mode 100644 index 0000000..8d9f7b7 --- /dev/null +++ b/datastar/templates/pages/index.html @@ -0,0 +1,15 @@ + + + + + + Lista de tareas + + + +
    Cargando...
    + +