feat: bulk update and click to edit

This commit is contained in:
joerdav
2023-04-18 10:23:18 +01:00
commit 60d3a869d2
15 changed files with 2865 additions and 0 deletions

64
bulkupdate/handlers.go Normal file
View File

@@ -0,0 +1,64 @@
package bulkupdate
import (
"log"
"net/http"
"strconv"
)
type user struct {
name, email string
active bool
}
var inMemDB []user = []user{
{"Joe Smith", "joe@smith.org", true},
{"Angie MacDowell", "angie@macdowell.org", true},
{"Fuqua Tarketon", "fuqua@tarketon.org", true},
{"Kim Yee", "kim@yee.org", false},
}
func Handlers(prefix string, mux *http.ServeMux) {
mux.HandleFunc(prefix+"/", index)
mux.HandleFunc(prefix+"/activate", putActivate)
mux.HandleFunc(prefix+"/deactivate", putDeactivate)
}
func index(w http.ResponseWriter, r *http.Request) {
// Load users
Index(inMemDB).Render(r.Context(), w)
}
func putActivate(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Println(err)
w.WriteHeader(500)
return
}
ids := map[int]bool{}
for _, i := range r.Form["ids"] {
id, _ := strconv.Atoi(i)
user := inMemDB[id]
user.active = true
inMemDB[id] = user
ids[id] = true
}
tbody(inMemDB, ids).Render(r.Context(), w)
}
func putDeactivate(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Println(err)
w.WriteHeader(500)
return
}
ids := map[int]bool{}
for _, i := range r.Form["ids"] {
id, _ := strconv.Atoi(i)
user := inMemDB[id]
user.active = false
inMemDB[id] = user
ids[id] = true
}
tbody(inMemDB, ids).Render(r.Context(), w)
}

View File

@@ -0,0 +1,91 @@
package bulkupdate
import (
"fmt"
"examples/shared"
)
templ demo(users []user) {
<h3 class="subtitle">Select Rows And Activate Or Deactivate Below</h3>
<form id="checked-contacts">
<table class="table">
<thead>
<tr>
<td></td>
<td>Name</td>
<td>Email</td>
<td>Status</td>
</tr>
</thead>
@tbody(users, map[int]bool{})
</table>
</form>
<div hx-swap="outerHTML" hx-include="#checked-contacts" hx-target="#tbody" class="field is-grouped">
<div class="control"><a class="button is-black" hx-put="/bulk-update/activate">Activate</a></div>
<div class="control"><a class="button" hx-put="/bulk-update/deactivate">Deactivate</a></div>
</div>
<style>
.htmx-settling tr.deactivate td {
background: lightcoral;
}
.htmx-settling tr.activate td {
background: darkseagreen;
}
tr td {
transition: all 1.2s;
}
</style>
}
templ tbody(users []user, modified map[int]bool) {
<tbody id="tbody">
for k, u := range users {
<tr
if modified[k] && u.active {
class="activate"
}
if modified[k] && !u.active {
class="deactivate"
}
>
<td><input type="checkbox" name="ids" value={ fmt.Sprint(k) }/></td>
<td>{ u.name }</td>
<td>{ u.email }</td>
<td>
if u.active {
Active
} else {
Inactive
}
</td>
</tr>
}
</tbody>
}
templ Index(users []user) {
@shared.Layout("Bulk Update") {
<h2 class="title">Bulk Update</h2>
<p>This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in <code>PUT</code>s to two different endpoints: <code>activate</code>and <code>deactivate</code>:</p>
<pre><code class="language-html">
@shared.Raw() {
@demo(users)
}
</code></pre>
<p>The server will either activate or deactivate the checked users and then rerender the <code>tbody</code>tag with updated rows. It will apply the class <code>activate</code>or <code>deactivate</code>to rows that have been mutated. This allows us to use a bit of CSS to flash a color helping the user see what happened:</p>
<pre><code class="language-css">{ `.htmx-settling tr.deactivate td {
background: lightcoral;
}
.htmx-settling tr.activate td {
background: darkseagreen;
}
tr td {
transition: all 1.2s;
}` }</code></pre>
<h2 class="title">Demo</h2>
@demo(users)
}
}

View File

@@ -0,0 +1,853 @@
// Code generated by templ@(devel) DO NOT EDIT.
package bulkupdate
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
// GoExpression
import (
"fmt"
"examples/shared"
)
func demo(users []user) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
templBuffer, templIsBuffer := w.(*bytes.Buffer)
if !templIsBuffer {
templBuffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templBuffer)
}
ctx = templ.InitializeContext(ctx)
var_1 := templ.GetChildren(ctx)
if var_1 == nil {
var_1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// Element (standard)
_, err = templBuffer.WriteString("<h3")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"subtitle\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Text
var_2 := `Select Rows And Activate Or Deactivate Below`
_, err = templBuffer.WriteString(var_2)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</h3>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<form")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" id=\"checked-contacts\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<table")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"table\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<thead>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<tr>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// Text
var_3 := `Name`
_, err = templBuffer.WriteString(var_3)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// Text
var_4 := `Email`
_, err = templBuffer.WriteString(var_4)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// Text
var_5 := `Status`
_, err = templBuffer.WriteString(var_5)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</tr>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</thead>")
if err != nil {
return err
}
// TemplElement
err = tbody(users, map[int]bool{}).Render(ctx, templBuffer)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</table>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</form>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<div")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" hx-swap=\"outerHTML\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" hx-include=\"#checked-contacts\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" hx-target=\"#tbody\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" class=\"field is-grouped\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<div")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"control\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<a")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"button is-black\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" hx-put=\"/bulk-update/activate\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Text
var_6 := `Activate`
_, err = templBuffer.WriteString(var_6)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</a>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</div>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<div")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"control\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<a")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"button\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" hx-put=\"/bulk-update/deactivate\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Text
var_7 := `Deactivate`
_, err = templBuffer.WriteString(var_7)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</a>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</div>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</div>")
if err != nil {
return err
}
// RawElement
_, err = templBuffer.WriteString("<style>")
if err != nil {
return err
}
// Text
var_8 := `
.htmx-settling tr.deactivate td {
background: lightcoral;
}
.htmx-settling tr.activate td {
background: darkseagreen;
}
tr td {
transition: all 1.2s;
}
`
_, err = templBuffer.WriteString(var_8)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</style>")
if err != nil {
return err
}
if !templIsBuffer {
_, err = io.Copy(w, templBuffer)
}
return err
})
}
func tbody(users []user, modified map[int]bool) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
templBuffer, templIsBuffer := w.(*bytes.Buffer)
if !templIsBuffer {
templBuffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templBuffer)
}
ctx = templ.InitializeContext(ctx)
var_9 := templ.GetChildren(ctx)
if var_9 == nil {
var_9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// Element (standard)
_, err = templBuffer.WriteString("<tbody")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" id=\"tbody\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// For
for k, u := range users {
// Element (standard)
_, err = templBuffer.WriteString("<tr")
if err != nil {
return err
}
// Element Attributes
if modified[k] && u.active {
// Element Attributes
_, err = templBuffer.WriteString(" class=\"activate\"")
if err != nil {
return err
}
}
if modified[k] && !u.active {
// Element Attributes
_, err = templBuffer.WriteString(" class=\"deactivate\"")
if err != nil {
return err
}
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// Element (void)
_, err = templBuffer.WriteString("<input")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" type=\"checkbox\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" name=\"ids\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(" value=")
if err != nil {
return err
}
_, err = templBuffer.WriteString("\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(templ.EscapeString(fmt.Sprint(k)))
if err != nil {
return err
}
_, err = templBuffer.WriteString("\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// StringExpression
var var_10 string = u.name
_, err = templBuffer.WriteString(templ.EscapeString(var_10))
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// StringExpression
var var_11 string = u.email
_, err = templBuffer.WriteString(templ.EscapeString(var_11))
if err != nil {
return err
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<td>")
if err != nil {
return err
}
// If
if u.active {
// Text
var_12 := `Active`
_, err = templBuffer.WriteString(var_12)
if err != nil {
return err
}
} else {
// Text
var_13 := `Inactive`
_, err = templBuffer.WriteString(var_13)
if err != nil {
return err
}
}
_, err = templBuffer.WriteString("</td>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</tr>")
if err != nil {
return err
}
}
_, err = templBuffer.WriteString("</tbody>")
if err != nil {
return err
}
if !templIsBuffer {
_, err = io.Copy(w, templBuffer)
}
return err
})
}
func Index(users []user) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
templBuffer, templIsBuffer := w.(*bytes.Buffer)
if !templIsBuffer {
templBuffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templBuffer)
}
ctx = templ.InitializeContext(ctx)
var_14 := templ.GetChildren(ctx)
if var_14 == nil {
var_14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
// TemplElement
var_15 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
templBuffer, templIsBuffer := w.(*bytes.Buffer)
if !templIsBuffer {
templBuffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templBuffer)
}
// Element (standard)
_, err = templBuffer.WriteString("<h2")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"title\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Text
var_16 := `Bulk Update`
_, err = templBuffer.WriteString(var_16)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</h2>")
if err != nil {
return err
}
// Whitespace (normalised)
_, err = templBuffer.WriteString(` `)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<p>")
if err != nil {
return err
}
// Text
var_17 := `This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in `
_, err = templBuffer.WriteString(var_17)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code>")
if err != nil {
return err
}
// Text
var_18 := `PUT`
_, err = templBuffer.WriteString(var_18)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
// Text
var_19 := `s to two different endpoints: `
_, err = templBuffer.WriteString(var_19)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code>")
if err != nil {
return err
}
// Text
var_20 := `activate`
_, err = templBuffer.WriteString(var_20)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
// Text
var_21 := `and `
_, err = templBuffer.WriteString(var_21)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code>")
if err != nil {
return err
}
// Text
var_22 := `deactivate`
_, err = templBuffer.WriteString(var_22)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
// Text
var_23 := `:`
_, err = templBuffer.WriteString(var_23)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</p>")
if err != nil {
return err
}
// Whitespace (normalised)
_, err = templBuffer.WriteString(` `)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<pre>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"language-html\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// TemplElement
var_24 := templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
templBuffer, templIsBuffer := w.(*bytes.Buffer)
if !templIsBuffer {
templBuffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templBuffer)
}
// TemplElement
err = demo(users).Render(ctx, templBuffer)
if err != nil {
return err
}
if !templIsBuffer {
_, err = io.Copy(w, templBuffer)
}
return err
})
err = shared.Raw().Render(templ.WithChildren(ctx, var_24), templBuffer)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</pre>")
if err != nil {
return err
}
// Whitespace (normalised)
_, err = templBuffer.WriteString(` `)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<p>")
if err != nil {
return err
}
// Text
var_25 := `The server will either activate or deactivate the checked users and then rerender the `
_, err = templBuffer.WriteString(var_25)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code>")
if err != nil {
return err
}
// Text
var_26 := `tbody`
_, err = templBuffer.WriteString(var_26)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
// Text
var_27 := `tag with updated rows. It will apply the class `
_, err = templBuffer.WriteString(var_27)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code>")
if err != nil {
return err
}
// Text
var_28 := `activate`
_, err = templBuffer.WriteString(var_28)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
// Text
var_29 := `or `
_, err = templBuffer.WriteString(var_29)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code>")
if err != nil {
return err
}
// Text
var_30 := `deactivate`
_, err = templBuffer.WriteString(var_30)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
// Text
var_31 := `to rows that have been mutated. This allows us to use a bit of CSS to flash a color helping the user see what happened:`
_, err = templBuffer.WriteString(var_31)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</p>")
if err != nil {
return err
}
// Whitespace (normalised)
_, err = templBuffer.WriteString(` `)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<pre>")
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<code")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"language-css\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// StringExpression
var var_32 string = `.htmx-settling tr.deactivate td {
background: lightcoral;
}
.htmx-settling tr.activate td {
background: darkseagreen;
}
tr td {
transition: all 1.2s;
}`
_, err = templBuffer.WriteString(templ.EscapeString(var_32))
if err != nil {
return err
}
_, err = templBuffer.WriteString("</code>")
if err != nil {
return err
}
_, err = templBuffer.WriteString("</pre>")
if err != nil {
return err
}
// Whitespace (normalised)
_, err = templBuffer.WriteString(` `)
if err != nil {
return err
}
// Element (standard)
_, err = templBuffer.WriteString("<h2")
if err != nil {
return err
}
// Element Attributes
_, err = templBuffer.WriteString(" class=\"title\"")
if err != nil {
return err
}
_, err = templBuffer.WriteString(">")
if err != nil {
return err
}
// Text
var_33 := `Demo`
_, err = templBuffer.WriteString(var_33)
if err != nil {
return err
}
_, err = templBuffer.WriteString("</h2>")
if err != nil {
return err
}
// Whitespace (normalised)
_, err = templBuffer.WriteString(` `)
if err != nil {
return err
}
// TemplElement
err = demo(users).Render(ctx, templBuffer)
if err != nil {
return err
}
if !templIsBuffer {
_, err = io.Copy(w, templBuffer)
}
return err
})
err = shared.Layout("Bulk Update").Render(templ.WithChildren(ctx, var_15), templBuffer)
if err != nil {
return err
}
if !templIsBuffer {
_, err = io.Copy(w, templBuffer)
}
return err
})
}