diff --git a/htmx-alpine/go.mod b/htmx-alpine/go.mod new file mode 100644 index 0000000..8077da4 --- /dev/null +++ b/htmx-alpine/go.mod @@ -0,0 +1,5 @@ +module htmx + +go 1.25.2 + +require github.com/zepyrshut/hrender v0.0.0-20251204145920-50fdd9cb5ff1 diff --git a/htmx-alpine/go.sum b/htmx-alpine/go.sum new file mode 100644 index 0000000..051a724 --- /dev/null +++ b/htmx-alpine/go.sum @@ -0,0 +1,4 @@ +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= diff --git a/htmx-alpine/main.go b/htmx-alpine/main.go new file mode 100644 index 0000000..b66c940 --- /dev/null +++ b/htmx-alpine/main.go @@ -0,0 +1,196 @@ +package main + +import ( + "log" + "net/http" + "os" + "sort" + "strconv" + "time" + + "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, + }, +} + +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.Render(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) { + w.Header().Set(hrender.ContentType, hrender.ContentTextHTMLUTF8) + + sort.Slice(todoList, func(i, j int) bool { + return todoList[i].ID > todoList[j].ID + }) + + err := h.Render(w, "fragments/todo-widget", hrender.H{ + "TodoList": todoList, + }) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + })) + + mux.Handle("GET /todo/new", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(hrender.ContentType, hrender.ContentTextHTMLUTF8) + err := h.Render(w, "fragments/todo-row-edit", hrender.H{}) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + })) + + mux.Handle("POST /todo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + newID := 0 + sort.Slice(todoList, func(i, j int) bool { + return todoList[i].ID > todoList[j].ID + }) + newID = todoList[0].ID + 1 + + log.Println(todoList[len(todoList)-1].ID) + log.Println(todoList) + + newItem := TodoList{ + ID: newID, + Name: r.FormValue("name"), + Completed: false, + } + + todoList = append(todoList, newItem) + + w.Header().Set(hrender.ContentType, hrender.ContentTextHTMLUTF8) + err = h.Render(w, "fragments/todo-row", hrender.H{ + "ID": newID, + "Name": newItem.Name, + "Completed": false, + }) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + })) + + mux.Handle("PATCH /todo/{id}/completed", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + 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 + } + + w.Header().Set(hrender.ContentType, hrender.ContentTextHTMLUTF8) + err = h.Render(w, "fragments/todo-row", hrender.H{ + "ID": foundItem.ID, + "Name": foundItem.Name, + "Completed": true, + }) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + })) + + mux.Handle("PUT /todo/{id}/update", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println("put triggered, sleeping 2 seconds") + time.Sleep(2 * time.Second) + + idStr := r.PathValue("id") + id, err := strconv.Atoi(idStr) + if err != nil { + http.Error(w, "invalid id", http.StatusBadRequest) + return + } + + if err := r.ParseForm(); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + newName := r.FormValue("name") + if newName == "some error" { + log.Println("error triggered") + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + var foundItem *TodoList + for i := range todoList { + if todoList[i].ID == id { + todoList[i].Name = newName + foundItem = &todoList[i] + break + } + } + + if foundItem == nil { + http.Error(w, "item not found", http.StatusNotFound) + return + } + + w.Header().Set(hrender.ContentType, hrender.ContentTextHTMLUTF8) + err = h.Render(w, "fragments/todo-row", hrender.H{ + "ID": foundItem.ID, + "Name": foundItem.Name, + "Completed": foundItem.Completed, + }) + if err != nil { + http.Error(w, "error loading template", http.StatusInternalServerError) + } + })) + + log.Println("server started on port 8080") + err := http.ListenAndServe(":8080", mux) + if err != nil { + panic("server cannot start") + } +} diff --git a/htmx-alpine/templates/fragments/todo-row.html b/htmx-alpine/templates/fragments/todo-row.html new file mode 100644 index 0000000..8189c99 --- /dev/null +++ b/htmx-alpine/templates/fragments/todo-row.html @@ -0,0 +1,44 @@ +