Put new invoice and edit invoice forms into a dialog

In this case i have to use the same id for the dialog content in all
pages because, for now, there are a couple of forms that need to replace
it on submit—the new/edit form and the product selection form.

Unfortunately, HTMx does not have support for `formaction` attribute at
this point, so i had to use the workaround described in [0].

[0] https://github.com/bigskysoftware/htmx/issues/623
This commit is contained in:
jordi fita mas 2023-03-31 13:01:26 +02:00
parent d1852a9703
commit 5717a5b9ed
6 changed files with 47 additions and 14 deletions

View File

@ -393,7 +393,7 @@ func mustRenderNewInvoiceForm(w http.ResponseWriter, r *http.Request, form *invo
locale := getLocale(r) locale := getLocale(r)
form.Customer.EmptyLabel = gettext("Select a customer to bill.", locale) form.Customer.EmptyLabel = gettext("Select a customer to bill.", locale)
page := newNewInvoicePage(form, r) page := newNewInvoicePage(form, r)
mustRenderAppTemplate(w, r, "invoices/new.gohtml", page) mustRenderModalTemplate(w, r, "invoices/new.gohtml", page)
} }
func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, action string, form *invoiceForm) { func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, action string, form *invoiceForm) {
@ -404,7 +404,7 @@ func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, ac
Form: form, Form: form,
Products: mustGetProductChoices(r.Context(), conn, company), Products: mustGetProductChoices(r.Context(), conn, company),
} }
mustRenderAppTemplate(w, r, "invoices/products.gohtml", page) mustRenderModalTemplate(w, r, "invoices/products.gohtml", page)
} }
func mustGetProductChoices(ctx context.Context, conn *Conn, company *Company) []*productChoice { func mustGetProductChoices(ctx context.Context, conn *Conn, company *Company) []*productChoice {
@ -457,8 +457,14 @@ func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Param
return return
} }
slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7, $8)", company.Id, form.Number, form.Date, form.Customer, form.Notes, form.PaymentMethod, form.Tags, NewInvoiceProductArray(form.Products)) slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7, $8)", company.Id, form.Number, form.Date, form.Customer, form.Notes, form.PaymentMethod, form.Tags, NewInvoiceProductArray(form.Products))
if IsHTMxRequest(r) {
w.Header().Set("HX-Trigger", "closeModal")
w.Header().Set("HX-Refresh", "true")
w.WriteHeader(http.StatusNoContent)
} else {
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther) http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
} }
}
func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
switch params[0].Value { switch params[0].Value {
@ -832,7 +838,12 @@ func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprout
if slug == "" { if slug == "" {
http.NotFound(w, r) http.NotFound(w, r)
} }
if IsHTMxRequest(r) {
w.Header().Set("HX-Refresh", "true")
w.WriteHeader(http.StatusNoContent)
} else {
http.Redirect(w, r, companyURI(mustGetCompany(r), "/invoices"), http.StatusSeeOther) http.Redirect(w, r, companyURI(mustGetCompany(r), "/invoices"), http.StatusSeeOther)
}
} else { } else {
slug := params[0].Value slug := params[0].Value
if !form.Validate() { if !form.Validate() {
@ -845,9 +856,15 @@ func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprout
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
if IsHTMxRequest(r) {
w.Header().Set("HX-Trigger", "closeModal")
w.Header().Set("HX-Refresh", "true")
w.WriteHeader(http.StatusNoContent)
} else {
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther) http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
} }
} }
}
func ServeEditInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func ServeEditInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
conn := getConn(r) conn := getConn(r)
@ -879,7 +896,7 @@ func newEditInvoicePage(slug string, form *invoiceForm, r *http.Request) *editIn
func mustRenderEditInvoiceForm(w http.ResponseWriter, r *http.Request, slug string, form *invoiceForm) { func mustRenderEditInvoiceForm(w http.ResponseWriter, r *http.Request, slug string, form *invoiceForm) {
page := newEditInvoicePage(slug, form, r) page := newEditInvoicePage(slug, form, r)
mustRenderAppTemplate(w, r, "invoices/edit.gohtml", page) mustRenderModalTemplate(w, r, "invoices/edit.gohtml", page)
} }
func HandleEditInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) { func HandleEditInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {

View File

@ -421,6 +421,19 @@ htmx.onLoad((target) => {
} }
}) })
htmx.on('htmx:configRequest', function(e) {
const element = e.detail.elt;
if (element && element.nodeName === 'FORM') {
let submitter = e.detail.triggeringEvent.submitter;
if (submitter) {
const action = submitter.attributes['formaction'];
if (action && action.value) {
e.detail.path = action.value;
}
}
}
})
htmx.on('closeModal', () => { htmx.on('closeModal', () => {
const openDialog = document.querySelector('dialog[open]'); const openDialog = document.querySelector('dialog[open]');
if (!openDialog) { if (!openDialog) {

View File

@ -15,9 +15,9 @@
{{ define "content" }} {{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editInvoicePage*/ -}} {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editInvoicePage*/ -}}
<section class="dialog-content"> <section class="dialog-content" id="invoice-dialog-content" data-hx-target="this">
<h2>{{ printf (pgettext "Edit Invoice “%s”" "title") .Number }}</h2> <h2>{{ printf (pgettext "Edit Invoice “%s”" "title") .Number }}</h2>
<form method="POST" action="{{ companyURI "/invoices/" }}{{ .Slug }}"> <form method="POST" action="{{ companyURI "/invoices/" }}{{ .Slug }}" data-hx-boost="true" data-hx-select="#invoice-dialog-content">
{{ csrfToken }} {{ csrfToken }}
{{ with .Form -}} {{ with .Form -}}

View File

@ -17,6 +17,7 @@
name="action" value="download" name="action" value="download"
>{{( pgettext "Download invoices" "action" )}}</button> >{{( pgettext "Download invoices" "action" )}}</button>
<a class="primary button" <a class="primary button"
data-hx-push-url="false" data-hx-swap="beforeend" data-hx-boost="true"
href="{{ companyURI "/invoices/new" }}">{{( pgettext "New invoice" "action" )}}</a> href="{{ companyURI "/invoices/new" }}">{{( pgettext "New invoice" "action" )}}</a>
</p> </p>
</form> </form>
@ -68,7 +69,7 @@
<td> <td>
<details class="invoice-status menu"> <details class="invoice-status menu">
<summary class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</summary> <summary class="invoice-status-{{ .Status }}">{{ .StatusLabel }}</summary>
<form action="{{companyURI "/invoices/"}}{{ .Slug }}" method="POST"> <form action="{{companyURI "/invoices/"}}{{ .Slug }}" method="POST" data-hx-boost="true">
{{ csrfToken }} {{ csrfToken }}
{{ putMethod }} {{ putMethod }}
<input type="hidden" name="quick" value="status"> <input type="hidden" name="quick" value="status">
@ -104,7 +105,9 @@
<summary><i class="ri-more-line"></i></summary> <summary><i class="ri-more-line"></i></summary>
<ul role="menu" class="action-menu"> <ul role="menu" class="action-menu">
<li role="presentation"> <li role="presentation">
<a role="menuitem" href="{{ companyURI "/invoices"}}/{{ .Slug }}/edit"> <a role="menuitem" href="{{ companyURI "/invoices"}}/{{ .Slug }}/edit"
data-hx-push-url="false" data-hx-swap="beforeend" data-hx-boost="true"
>
<i class="ri-edit-line"></i> <i class="ri-edit-line"></i>
{{( pgettext "Edit" "action" )}} {{( pgettext "Edit" "action" )}}
</a> </a>

View File

@ -15,9 +15,9 @@
{{ define "content" }} {{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoicePage*/ -}} {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoicePage*/ -}}
<section class="dialog-content"> <section class="dialog-content" id="invoice-dialog-content" data-hx-target="this">
<h2>{{(pgettext "New Invoice" "title")}}</h2> <h2>{{(pgettext "New Invoice" "title")}}</h2>
<form method="POST" action="{{ companyURI "/invoices" }}"> <form method="POST" action="{{ companyURI "/invoices" }}" data-hx-boost="true" data-hx-select="#invoice-dialog-content">
{{ csrfToken }} {{ csrfToken }}
{{ with .Form -}} {{ with .Form -}}

View File

@ -19,9 +19,9 @@
{{ define "content" }} {{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoiceProductsPage*/ -}} {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoiceProductsPage*/ -}}
<section class="dialog-content"> <section class="dialog-content" id="invoice-dialog-content" data-hx-target="this">
<h2>{{(pgettext "Add Products to Invoice" "title")}}</h2> <h2>{{(pgettext "Add Products to Invoice" "title")}}</h2>
<form method="POST" action="{{ .Action }}"> <form method="POST" action="{{ .Action }}" data-hx-boost="true" data-hx-select="#invoice-dialog-content">
{{ csrfToken }} {{ csrfToken }}
{{- with .Form }} {{- with .Form }}