Handle errors on htmx requests (#36)

- Use grouping for sets of links (#38)
This commit is contained in:
Daniel J. Summers 2022-08-15 22:29:06 -04:00
parent 1b48acd66a
commit d0068577b3
6 changed files with 77 additions and 56 deletions

View File

@ -150,6 +150,9 @@ let _inputField = _inputFieldWith []
/// The class that designates a checkbox / label pair /// The class that designates a checkbox / label pair
let _checkboxField = _class "pt-checkbox-field" let _checkboxField = _class "pt-checkbox-field"
/// A group of related fields, inputs, links, etc., displayed in a row
let _group = _class "pt-group"
/// Create an input field of the given type, with matching name and ID and the given value /// Create an input field of the given type, with matching name and ID and the given value
let inputField typ name value attrs = let inputField typ name value attrs =
List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input

View File

@ -54,7 +54,7 @@ let edit (model : EditRequest) today ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Expiration"] ] label [] [ locStr s["Expiration"] ]
span [ _class "pt-radio-group" ] [ span [ _group ] [
for code, name in ReferenceList.expirationList s (not model.IsNew) do for code, name in ReferenceList.expirationList s (not model.IsNew) do
label [] [ radio (nameof model.Expiration) "" code model.Expiration; locStr name ] label [] [ radio (nameof model.Expiration) "" code model.Expiration; locStr name ]
] ]
@ -212,22 +212,22 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
] ]
]) ])
|> List.ofSeq |> List.ofSeq
[ div [ _class "pt-center-text" ] [ [ br []
br [] div [ _fieldRow ] [
a [ _href $"/prayer-request/{emptyGuid}/edit"; _title s["Add a New Request"].Value ] [ span [ _group ] [
icon "add_circle"; rawText " &nbsp;"; locStr s["Add a New Request"] a [ _href $"/prayer-request/{emptyGuid}/edit"; _title s["Add a New Request"].Value ] [
] icon "add_circle"; rawText " &nbsp;"; locStr s["Add a New Request"]
rawText " &nbsp; &nbsp; &nbsp; "
a [ _href "/prayer-requests/view"; _title s["View Prayer Request List"].Value ] [
icon "list"; rawText " &nbsp;"; locStr s["View Prayer Request List"]
]
match model.SearchTerm with
| Some _ ->
rawText " &nbsp; &nbsp; &nbsp; "
a [ _href "/prayer-requests"; _title l["Clear Search Criteria"].Value ] [
icon "highlight_off"; rawText " &nbsp;"; raw l["Clear Search Criteria"]
] ]
| None -> () a [ _href "/prayer-requests/view"; _title s["View Prayer Request List"].Value ] [
icon "list"; rawText " &nbsp;"; locStr s["View Prayer Request List"]
]
match model.SearchTerm with
| Some _ ->
a [ _href "/prayer-requests"; _title l["Clear Search Criteria"].Value ] [
icon "highlight_off"; rawText " &nbsp;"; raw l["Clear Search Criteria"]
]
| None -> ()
]
] ]
form [ _action "/prayer-requests"; _method "get"; _class "pt-center-text pt-search-form"; Target.content ] [ form [ _action "/prayer-requests"; _method "get"; _class "pt-center-text pt-search-form"; Target.content ] [
inputField "text" "search" (defaultArg model.SearchTerm "") [ _placeholder l["Search requests..."].Value ] inputField "text" "search" (defaultArg model.SearchTerm "") [ _placeholder l["Search requests..."].Value ]
@ -300,9 +300,9 @@ let print model version =
br [] br []
hr [] hr []
div [ _style $"font-size:70%%;font-family:{model.SmallGroup.Preferences.FontStack};" ] [ div [ _style $"font-size:70%%;font-family:{model.SmallGroup.Preferences.FontStack};" ] [
img [ _src $"""/img/{s["footer_en"].Value}.png""" img [ _src $"""/img/{s["footer_en"].Value}.png"""
_style "vertical-align:text-bottom;" _style "vertical-align:text-bottom;"
_alt imgAlt _alt imgAlt
_title imgAlt ] _title imgAlt ]
space space
str version str version
@ -315,38 +315,36 @@ let print model version =
let view model viewInfo = let view model viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
let pageTitle = $"""{s["Prayer Requests"].Value} {model.SmallGroup.Name}""" let pageTitle = $"""{s["Prayer Requests"].Value} {model.SmallGroup.Name}"""
let spacer = rawText " &nbsp; &nbsp; &nbsp; "
let dtString = model.Date.ToString ("yyyy-MM-dd", CultureInfo.InvariantCulture) let dtString = model.Date.ToString ("yyyy-MM-dd", CultureInfo.InvariantCulture)
[ div [ _class "pt-center-text" ] [ [ br []
br [] div [ _fieldRow ] [
a [ _class "pt-icon-link" span [ _group ] [
_href $"/prayer-requests/print/{dtString}" a [ _class "pt-icon-link"
_target "_blank" _href $"/prayer-requests/print/{dtString}"
_title s["View Printable"].Value ] [ _target "_blank"
icon "print"; rawText " &nbsp;"; locStr s["View Printable"] _title s["View Printable"].Value ] [
] icon "print"; rawText " &nbsp;"; locStr s["View Printable"]
if model.CanEmail then
spacer
if model.Date.DayOfWeek <> IsoDayOfWeek.Sunday then
let rec findSunday (date : LocalDate) =
if date.DayOfWeek = IsoDayOfWeek.Sunday then date else findSunday (date.PlusDays 1)
let sunday = findSunday model.Date
a [ _class "pt-icon-link"
_href $"""/prayer-requests/view/{sunday.ToString ("yyyy-MM-dd", CultureInfo.InvariantCulture)}"""
_title s["List for Next Sunday"].Value ] [
icon "update"; rawText " &nbsp;"; locStr s["List for Next Sunday"]
]
spacer
let emailPrompt = s["This will e-mail the current list to every member of your group, without further prompting. Are you sure this is what you are ready to do?"].Value
a [ _class "pt-icon-link"
_href $"/prayer-requests/email/{dtString}"
_title s["Send via E-mail"].Value
_onclick $"return PT.requests.view.promptBeforeEmail('{emailPrompt}')" ] [
icon "mail_outline"; rawText " &nbsp;"; locStr s["Send via E-mail"]
] ]
spacer if model.CanEmail then
a [ _class "pt-icon-link"; _href "/prayer-requests"; _title s["Maintain Prayer Requests"].Value ] [ if model.Date.DayOfWeek <> IsoDayOfWeek.Sunday then
icon "compare_arrows"; rawText " &nbsp;"; locStr s["Maintain Prayer Requests"] let rec findSunday (date : LocalDate) =
if date.DayOfWeek = IsoDayOfWeek.Sunday then date else findSunday (date.PlusDays 1)
let sunday = findSunday model.Date
a [ _class "pt-icon-link"
_href $"""/prayer-requests/view/{sunday.ToString ("yyyy-MM-dd", CultureInfo.InvariantCulture)}"""
_title s["List for Next Sunday"].Value ] [
icon "update"; rawText " &nbsp;"; locStr s["List for Next Sunday"]
]
let emailPrompt = s["This will e-mail the current list to every member of your group, without further prompting. Are you sure this is what you are ready to do?"].Value
a [ _class "pt-icon-link"
_href $"/prayer-requests/email/{dtString}"
_title s["Send via E-mail"].Value
_onclick $"return PT.requests.view.promptBeforeEmail('{emailPrompt}')" ] [
icon "mail_outline"; rawText " &nbsp;"; locStr s["Send via E-mail"]
]
a [ _class "pt-icon-link"; _href "/prayer-requests"; _title s["Maintain Prayer Requests"].Value ] [
icon "compare_arrows"; rawText " &nbsp;"; locStr s["Maintain Prayer Requests"]
]
] ]
] ]
br [] br []

View File

@ -30,7 +30,7 @@ let announcement isAdmin ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Send Announcement to"]; rawText ":" ] label [] [ locStr s["Send Announcement to"]; rawText ":" ]
div [ _class "pt-radio-group" ] [ div [ _group ] [
label [] [ label [] [
radio (nameof model.SendToClass) $"{nameof model.SendToClass}_Y" "Y" "Y" radio (nameof model.SendToClass) $"{nameof model.SendToClass}_Y" "Y" "Y"
locStr s["This Group"] locStr s["This Group"]
@ -457,7 +457,7 @@ let preferences (model : EditPreferences) ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Color of Heading Lines"] ] label [] [ locStr s["Color of Heading Lines"] ]
span [ _class "pt-radio-group" ] [ span [ _group ] [
span [] [ span [] [
label [] [ label [] [
radio (nameof model.LineColorType) $"{nameof model.LineColorType}_Name" "Name" radio (nameof model.LineColorType) $"{nameof model.LineColorType}_Name" "Name"
@ -488,7 +488,7 @@ let preferences (model : EditPreferences) ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Color of Heading Text"] ] label [] [ locStr s["Color of Heading Text"] ]
span [ _class "pt-radio-group" ] [ span [ _group ] [
span [] [ span [] [
label [] [ label [] [
radio (nameof model.HeadingColorType) $"{nameof model.HeadingColorType}_Name" "Name" radio (nameof model.HeadingColorType) $"{nameof model.HeadingColorType}_Name" "Name"
@ -522,14 +522,14 @@ let preferences (model : EditPreferences) ctx viewInfo =
div [ _inputField ] [ div [ _inputField ] [
label [ _for (nameof model.Fonts) ] [ locStr s["Fonts** for List"] ] label [ _for (nameof model.Fonts) ] [ locStr s["Fonts** for List"] ]
let value = if model.IsNative then "True" else "False" let value = if model.IsNative then "True" else "False"
span [ _class "pt-radio-group" ] [ span [ _group ] [
label [] [ label [] [
radio (nameof model.IsNative) $"{nameof model.IsNative}_Y" "True" value radio (nameof model.IsNative) $"{nameof model.IsNative}_Y" "True" value
locStr s["Native Fonts"] locStr s["Native Fonts"]
] ]
inputField "text" "nativeFontSpacer" "" [ _style "visibility:hidden" ] inputField "text" "nativeFontSpacer" "" [ _style "visibility:hidden" ]
] ]
span [ _class "pt-radio-group" ] [ span [ _group ] [
label [] [ label [] [
radio (nameof model.IsNative) $"{nameof model.IsNative}_N" "False" value radio (nameof model.IsNative) $"{nameof model.IsNative}_N" "False" value
locStr s["Named Fonts"] locStr s["Named Fonts"]
@ -557,7 +557,7 @@ let preferences (model : EditPreferences) ctx viewInfo =
div [ _fieldRow ] [ div [ _fieldRow ] [
div [ _inputField ] [ div [ _inputField ] [
label [] [ locStr s["Request List Visibility"] ] label [] [ locStr s["Request List Visibility"] ]
span [ _class "pt-radio-group" ] [ span [ _group ] [
let name = nameof model.Visibility let name = nameof model.Visibility
let value = string model.Visibility let value = string model.Visibility
label [] [ label [] [

View File

@ -126,7 +126,7 @@ let expire reqId : HttpHandler = requireAccess [ User ] >=> fun next ctx -> task
/// GET /prayer-requests/[group-id]/list /// GET /prayer-requests/[group-id]/list
let list groupId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task { let list groupId : HttpHandler = requireAccess [ AccessLevel.Public ] >=> fun next ctx -> task {
match! SmallGroups.tryByIdWithPreferences groupId ctx.Conn with match! SmallGroups.tryByIdWithPreferences (SmallGroupId groupId) ctx.Conn with
| Some group when group.Preferences.IsPublic -> | Some group when group.Preferences.IsPublic ->
let! reqs = let! reqs =
PrayerRequests.forGroup PrayerRequests.forGroup

View File

@ -254,7 +254,7 @@ footer a:hover {
text-transform: uppercase; text-transform: uppercase;
color: #777; color: #777;
} }
.pt-radio-group { .pt-group {
display: flex; display: flex;
flex-flow: row; flex-flow: row;
gap: 1.5rem; gap: 1.5rem;

View File

@ -301,3 +301,23 @@ this.PT = {
htmx.on("htmx:configRequest", function (e) { htmx.on("htmx:configRequest", function (e) {
e.detail.headers["X-Target"] = e.detail.target e.detail.headers["X-Target"] = e.detail.target
}) })
htmx.on("htmx:responseError", function (e) {
/** @type {XMLHttpRequest} */
const xhr = e.detail.xhr
const detail = document.createElement("div")
detail.className = "description"
detail.innerHTML = `<em>(Status code ${xhr.status}: ${xhr.statusText})</em>`
const msg = document.createElement("div")
msg.className = "pt-msg error"
msg.innerHTML = `<strong>ERROR</strong> &#xbb; ${xhr.responseText}<br>`
msg.appendChild(detail)
const messages = document.createElement("div")
messages.className = "pt-messages"
messages.appendChild(msg)
const title = document.getElementById("pt-page-title")
title.parentNode.insertBefore(messages, title.nextSibling)
})