Browse Source

Index mostly working except Archiver progress. Edit GET showing.

Frédéric G. MARAND 10 months ago
parent
commit
2aa9c8dad6

+ 1 - 1
.idea/runConfigurations/go_build_code_osinet_fr_fgm_demo_htmx_contactsapp.xml → .idea/runConfigurations/Contacts_app.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="go build code.osinet.fr/fgm/demo-htmx/contactsapp" type="GoApplicationRunConfiguration" factoryName="Go Application" nameIsGenerated="true">
+  <configuration default="false" name="Contacts app" type="GoApplicationRunConfiguration" factoryName="Go Application">
     <module name="demo-htmx" />
     <working_directory value="$PROJECT_DIR$/contactsapp" />
     <EXTENSION ID="net.ashald.envfile">

+ 21 - 0
.idea/runConfigurations/Demo_HTMX.xml

@@ -0,0 +1,21 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Demo HTMX" type="GoApplicationRunConfiguration" factoryName="Go Application">
+    <module name="demo-htmx" />
+    <working_directory value="$PROJECT_DIR$" />
+    <EXTENSION ID="net.ashald.envfile">
+      <option name="IS_ENABLED" value="false" />
+      <option name="IS_SUBST" value="false" />
+      <option name="IS_PATH_MACRO_SUPPORTED" value="false" />
+      <option name="IS_IGNORE_MISSING_FILES" value="false" />
+      <option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
+      <ENTRIES>
+        <ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
+      </ENTRIES>
+    </EXTENSION>
+    <kind value="FILE" />
+    <package value="code.osinet.fr/fgm/demo-htmx" />
+    <directory value="$PROJECT_DIR$" />
+    <filePath value="$PROJECT_DIR$/main.go" />
+    <method v="2" />
+  </configuration>
+</component>

+ 57 - 30
contactsapp/app.go

@@ -14,9 +14,60 @@ import (
 )
 
 const (
+	App       = "contactsapp"
 	SecretKey = "hypermedia rocks"
 )
 
+func MakeTemplate(names ...string) *template.Template {
+	names = append([]string{"layout"}, names...)
+	paths := make([]string, len(names))
+	for i, name := range names {
+		paths[i] = fmt.Sprintf("./templates/%s.gohtml", name)
+	}
+	tpl := template.Must(template.New("layout.html").
+		Funcs(sprig.FuncMap()).
+		Funcs(template.FuncMap{
+			"get_flashed_messages": func() []string {
+				return []string{"some message"}
+			},
+		}).
+		ParseFiles(paths...),
+	)
+	return tpl
+}
+
+/*
+@app.route("/contacts")
+def contacts():
+*/
+func RouteContacts(w http.ResponseWriter, r *http.Request) {
+	search := r.URL.Query().Get("q")
+	page, err := strconv.Atoi(r.URL.Query().Get("page"))
+	if err != nil || page == 0 {
+		page = 1
+	}
+	var contactsSet []Contact
+	if search != "" {
+		contactsSet = (&Contact{}).Search(search)
+		if r.Header.Get("HX-Trigger") == "search" {
+			MakeTemplate("rows").Execute(w, anyMap{"contacts": contactsSet})
+			return
+		}
+	} else {
+		contactsSet = (&Contact{}).All(page)
+	}
+	MakeTemplate(
+		"index",
+		"archive_ui",
+		"rows",
+	).Execute(w, anyMap{
+		"contacts": contactsSet,
+		"search":   search,
+		"page":     page,
+		"archiver": NewArchiver(),
+	})
+}
+
 var (
 	/*
 	   @app.route("/")
@@ -24,34 +75,6 @@ var (
 	*/
 	RouteFront = http.RedirectHandler("/contacts", http.StatusFound)
 
-	/*
-	   @app.route("/contacts")
-	   def contacts():
-	*/
-	MakeRouteContacts = func(templates *template.Template) http.HandlerFunc {
-		return func(w http.ResponseWriter, r *http.Request) {
-			search := r.URL.Query().Get("q")
-			page, err := strconv.Atoi(r.URL.Query().Get("page"))
-			if err != nil || page == 0 {
-				page = 1
-			}
-			var contactsSet []Contact
-			if search != "" {
-				contactsSet = (&Contact{}).Search(search)
-				if r.Header.Get("HX-Trigger") == "search" {
-					templates.ExecuteTemplate(w, "rows.html", anyMap{"contacts": contactsSet})
-					return
-				}
-			} else {
-				contactsSet = (&Contact{}).All(page)
-			}
-			templates.ExecuteTemplate(w, "index.html", anyMap{
-				"contacts": contactsSet,
-				"archiver": NewArchiver(),
-			})
-		}
-	}
-
 	/*
 		@app.route("/contacts/archive", methods=["POST"])
 		def start_archive():
@@ -159,7 +182,10 @@ var (
 		return func(w http.ResponseWriter, r *http.Request) {
 			contact_id, _ := strconv.Atoi(r.PathValue("contact_id"))
 			contact := (&Contact{}).Find(uint64(contact_id))
-			templates.ExecuteTemplate(w, "edit.html", anyMap{"contact": contact})
+			MakeTemplate(
+				"edit",
+			).
+				Execute(w, anyMap{"contact": contact})
 		}
 	}
 
@@ -336,7 +362,8 @@ func flash(message string) {
 
 func setupRoutes(mux *http.ServeMux, templates *template.Template) {
 	mux.Handle("/", RouteFront)
-	mux.Handle("/contacts", MakeRouteContacts(templates))
+	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
+	mux.HandleFunc("/contacts", RouteContacts)
 	mux.Handle("POST /contacts/archive", MakeStartArchive(templates))
 	mux.Handle("GET /contacts/archive", MakeArchiveStatus(templates))
 	mux.Handle("GET /contacts/archive/file", ArchiveContent)

File diff suppressed because it is too large
+ 0 - 138
contactsapp/contacts.json


+ 7 - 4
contactsapp/contacts_model.go

@@ -5,7 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"log"
-	"math/rand/v2"
+	"math/rand"
 	"os"
 	"slices"
 	"strings"
@@ -151,7 +151,7 @@ func (*Contact) All(page int) []Contact {
 		page = 1
 	}
 	start := (page - 1) * PageSize
-	end := start + PageSize
+	end := min(start+PageSize, start+len(db))
 	ids := make([]uint64, 0, len(db))
 	for id := range db {
 		ids = append(ids, id)
@@ -285,15 +285,17 @@ func (self *Archiver) Run() {
 def run_impl(self):
 */
 func (self *Archiver) RunImpl() {
+	t0 := time.Now()
 	for i := range 10 {
-		time.Sleep(time.Duration(rand.Float64()) * time.Second)
+		delay := time.Duration(rand.Float64() * float64(time.Second))
+		time.Sleep(delay)
 		self.Lock()
 		if self.archiveStatus != statusRunning {
 			self.Unlock()
 			return
 		}
 		self.archiveProgress = float64(i+1) / 10.0
-		log.Println("Here... ", self.archiveProgress)
+		log.Printf("Here... after delay %v, cum %v: %f", delay, time.Since(t0), self.archiveProgress)
 		self.Unlock()
 	}
 	time.Sleep(time.Second)
@@ -304,6 +306,7 @@ func (self *Archiver) RunImpl() {
 	}
 	self.archiveStatus = statusComplete
 	self.Unlock()
+	log.Printf("RunImpl took %v", time.Since(t0))
 }
 
 /*

+ 14 - 13
contactsapp/templates/archive_ui.gohtml

@@ -1,19 +1,20 @@
 {{define "archive_ui.html"}}
-<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
-    {% if archiver.status() == "Waiting" %}
+
+  <div id="archive-ui" hx-target="this" hx-swap="outerHTML">
+      {{ if eq .archiver.Status "Waiting" }}
         <button hx-post="/contacts/archive">
-            Download Contact Archive
+          Download Contact Archive
         </button>
-    {% elif archiver.status() == "Running" %}
+      {{ else if eq .archiver.Status "Running" }}
         <div hx-get="/contacts/archive" hx-trigger="load delay:500ms">
-            Creating Archive...
-            <div class="progress" >
-                <div id="archive-progress" class="progress-bar" style="width:{{ mul .archiver.progress 100 }}%"></div>
-            </div>
+          Creating Archive...{{ printf "%15.5f" .archiver.Progress }}
+          <div class="progress">
+            <div id="archive-progress" class="progress-bar" style="width:{{ mul .archiver.Progress 100 }}%"></div>
+          </div>
         </div>
-    {% elif archiver.status() == "Complete" %}
-        <a hx-boost="false" href="/contacts/archive/file" _="on load click() me">Archive Downloading!  Click here if the download does not start.</a>
+      {{ else if eq .archiver.Status "Complete" }}
+        <a hx-boost="false" href="/contacts/archive/file" _="on load click() me">Archive Downloading! Click here if the download does not start.</a>
         <button hx-delete="/contacts/archive">Clear Download</button>
-    {% endif %}
-</div>
-{{end}}
+      {{ end }}
+  </div>
+{{end}}

+ 12 - 11
contactsapp/templates/edit.gohtml

@@ -1,36 +1,37 @@
 {{define "edit.html"}}
+    <h1>EDIT</h1>
 {% extends 'layout.html' %}
 
 {{ block "content" . }}
 
-    <form action="/contacts/{{ .contact.id }}/edit" method="post">
+    <form action="/contacts/{{ .contact.ID }}/edit" method="post">
         <fieldset>
             <legend>Contact Values</legend>
             <div class="table rows">
                 <p>
                     <label for="email">Email</label>
                     <input name="email" id="email" type="email"
-                           hx-get="/contacts/{{ .contact.id }}/email" hx-target="next .error"
+                           hx-get="/contacts/{{ .contact.ID }}/email" hx-target="next .error"
                            hx-trigger="change, keyup delay:200ms"
-                           placeholder="Email" value="{{ .contact.email }}">
-                    <span class="error">{{ .contact.errors.email }}</span>
+                           placeholder="Email" value="{{ .contact.Email }}">
+{{/*                    <span class="error">{{ .contact.Errors.email }}</span>*/}}
                 </p>
                 <p>
                     <label for="first_name">First Name</label>
                     <input name="first_name" id="first_name" type="text" placeholder="First Name"
-                           value="{{ .contact.first }}">
-                    <span class="error">{{ .contact.errors.first }}</span>
+                           value="{{ .contact.First }}">
+{{/*                    <span class="error">{{ .contact.Errors.first }}</span>*/}}
                 </p>
                 <p>
                     <label for="last_name">Last Name</label>
                     <input name="last_name" id="last_name" type="text" placeholder="Last Name"
-                           value="{{ .contact.last }}">
-                    <span class="error">{{ .contact.errors.last }}</span>
+                           value="{{ .contact.Last }}">
+{{/*                    <span class="error">{{ .contact.Errors.last }}</span>*/}}
                 </p>
                 <p>
                     <label for="phone">Phone</label>
-                    <input name="phone" id="phone" type="text" placeholder="Phone" value="{{ .contact.phone }}">
-                    <span class="error">{{ .contact.errors.phone }}</span>
+                    <input name="phone" id="phone" type="text" placeholder="Phone" value="{{ .contact.Phone }}">
+{{/*                    <span class="error">{{ .contact.Errors.phone }}</span>*/}}
                 </p>
             </div>
             <button>Save</button>
@@ -38,7 +39,7 @@
     </form>
 
     <button id="delete-btn"
-            hx-delete="/contacts/{{ .contact.id }}"
+            hx-delete="/contacts/{{ .contact.ID }}"
             hx-push-url="true"
             hx-confirm="Are you sure you want to delete this contact?"
             hx-target="body">

+ 3 - 5
contactsapp/templates/index.gohtml

@@ -1,13 +1,11 @@
 {{define "index.html"}}
-{% extends 'layout.html' %}
-
 {{ block "content" . }}
 
-    {% include 'archive_ui.html' %}
+    {{ template "archive_ui.html" . }}
 
     <form action="/contacts" method="get" class="tool-bar">
         <label for="search">Search Term</label>
-        <input id="search" type="search" name="q" value="{{ .request.args.get 'q' }}"
+        <input id="search" type="search" name="q" value="{{ .search }}"
                hx-get="/contacts"
                hx-trigger="search, keyup delay:200ms changed"
                hx-target="tbody"
@@ -44,7 +42,7 @@
         </tr>
         </thead>
         <tbody>
-        {% include 'rows.html' %}
+        {{ template "rows.html" . }}
         </tbody>
     </table>
         <button hx-delete="/contacts"

+ 23 - 19
contactsapp/templates/layout.gohtml

@@ -1,26 +1,30 @@
-<!doctype html>
-<html lang="">
-<head>
+{{ define "layout.html" }}
+  <!DOCTYPE html>
+  <html lang="">
+  <head>
     <title>Contact App</title>
-    <link rel="stylesheet" href="https://the.missing.style/v0.2.0/missing.min.css">
+    <!-- link rel="stylesheet" href="https://the.missing.style/v0.2.0/missing.min.css" -->
+    <link rel="stylesheet" href="https://unpkg.com/missing.css@0.2.0/missing.min.css">
     <link rel="stylesheet" href="/static/site.css">
     <script src="/static/js/htmx-1.8.0.js"></script>
     <script src="/static/js/_hyperscript-0.9.7.js"></script>
     <script src="/static/js/rsjs-menu.js" type="module"></script>
-    <script defer src="https://unpkg.com/alpinejs@3/dist/cdn.min.js"></script>
-</head>
-<body hx-boost="true">
-<main>
+    <!-- script defer src="https://unpkg.com/alpinejs@3/dist/cdn.min.js"></script -->
+    <script defer src="https://unpkg.com/alpinejs@3.13.10/dist/cdn.min.js"></script>
+  </head>
+  <body hx-boost="true">
+  <main>
     <header>
-        <h1>
-            <all-caps>contacts.app</all-caps>
-            <sub-title>A Demo Contacts Application</sub-title>
-        </h1>
+      <h1>
+        <all-caps>contacts.app</all-caps>
+        <sub-title>A Demo Contacts Application</sub-title>
+      </h1>
     </header>
-    {{ range $message := get_flashed_messages }}
-      <div class="flash">{{ $message }}</div>
-    {{ end }}
-    {{block "content" . }}{{ end }}
-</main>
-</body>
-</html>
+      {{ range $message := get_flashed_messages }}
+        <div class="flash">{{ $message }}</div>
+      {{ end }}
+      {{block "content" . }}Default content{{ end }}
+  </main>
+  </body>
+  </html>
+{{ end }}

+ 30 - 23
contactsapp/templates/rows.gohtml

@@ -1,28 +1,35 @@
 {{define "rows.html"}}
-{% for contact in contacts %}
-    <tr>
-        <td><input type="checkbox" name="selected_contact_ids" value="{{ .contact.id }}"
-            x-model="selected"></td>
-        <td>{{ .contact.first }}</td>
-        <td>{{ .contact.last }}</td>
-        <td>{{ .contact.phone }}</td>
-        <td>{{ .contact.email }}</td>
+    {{ range $k, $contact := .contacts }}
+      <tr>
+        <td><input type="checkbox"
+                   name="selected_contact_ids"
+                   value="{{ $contact.ID }}"
+                   x-model="selected"></td>
+        <td>{{ $contact.First }}</td>
+        <td>{{ $contact.Last }}</td>
+        <td>{{ $contact.Phone }}</td>
+        <td>{{ $contact.Email }}</td>
         <td>
-            <div data-overflow-menu>
-                <button type="button" aria-haspopup="menu"
-                    aria-controls="contact-menu-{{ .contact.id }}"
-                    >Options</button>
-                <div role="menu" hidden id="contact-menu-{{ .contact.id }}">
-                    <a role="menuitem" href="/contacts/{{ .contact.id }}/edit">Edit</a>
-                    <a role="menuitem" href="/contacts/{{ .contact.id }}">View</a>
-                    <a role="menuitem" href="#"
-                        hx-delete="/contacts/{{ .contact.id }}"
-                        hx-confirm="Are you sure you want to delete this contact?"
-                        hx-swap="outerHTML swap:1s"
-                        hx-target="closest tr">Delete</a>
-                </div>
+          <div data-overflow-menu>
+            <button type="button" aria-haspopup="menu"
+                    aria-controls="contact-menu-{{ $contact.ID }}"
+            >Options
+            </button>
+            <div role="menu" hidden id="contact-menu-{{ $contact.ID }}">
+              <a role="menuitem" href="/contacts/{{ $contact.ID }}/edit">Edit</a>
+              <a role="menuitem" href="/contacts/{{ $contact.ID }}">View</a>
+              <a role="menuitem" href="#"
+                 hx-delete="/contacts/{{ $contact.ID }}"
+                 hx-confirm="Are you sure you want to delete this contact?"
+                 hx-swap="outerHTML swap:1s"
+                 hx-target="closest tr">Delete</a>
             </div>
+          </div>
         </td>
-    </tr>
-{% endfor %}
+      </tr>
+    {{ else }}
+      <tr>
+        <td>No matching rows</td>
+      </tr>
+    {{end}}
 {{end}}

+ 1 - 1
doc/htmx-ch04.md

@@ -1,5 +1,5 @@
 # HTMX chapter 4
-## Attributes
+## Starter Attributes
 
 - `hx-[delete|get|patch|post|put]` - AJAX method to use
 - `hx-swap`

Some files were not shown because too many files changed in this diff