From 7786896dfd658a45df2c40e1265038950b72a973 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Mon, 1 Aug 2022 16:22:37 -0400 Subject: [PATCH] Initial htmx nav works (#36) - Use nameof for field IDs - Add helper functions to streamline forms --- src/PrayerTracker.UI/Church.fs | 53 ++---- src/PrayerTracker.UI/CommonFunctions.fs | 15 ++ src/PrayerTracker.UI/Layout.fs | 34 ++-- src/PrayerTracker.UI/PrayerRequest.fs | 50 ++--- src/PrayerTracker.UI/SmallGroup.fs | 235 ++++++++++-------------- src/PrayerTracker.UI/User.fs | 118 +++++------- src/PrayerTracker/App.fs | 12 +- src/PrayerTracker/CommonFunctions.fs | 5 +- src/PrayerTracker/wwwroot/js/app.js | 74 ++++---- 9 files changed, 259 insertions(+), 337 deletions(-) diff --git a/src/PrayerTracker.UI/Church.fs b/src/PrayerTracker.UI/Church.fs index a3e88c1..4ea6d3d 100644 --- a/src/PrayerTracker.UI/Church.fs +++ b/src/PrayerTracker.UI/Church.fs @@ -11,10 +11,10 @@ let edit (model : EditChurch) ctx viewInfo = let vi = viewInfo |> AppViewInfo.withScopedStyles [ - "#name { width: 20rem; }" - "#city { width: 10rem; }" - "#st { width: 3rem; }" - "#interfaceAddress { width: 30rem; }" + $"#{nameof model.Name} {{ width: 20rem; }}" + $"#{nameof model.City} {{ width: 10rem; }}" + $"#{nameof model.State} {{ width: 3rem; }}" + $"#{nameof model.InterfaceAddress} {{ width: 30rem; }}" ] |> AppViewInfo.withOnLoadScript "PT.church.edit.onPageLoad" form [ _action "/church/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ @@ -22,40 +22,29 @@ let edit (model : EditChurch) ctx viewInfo = input [ _type "hidden"; _name (nameof model.ChurchId); _value (flatGuid model.ChurchId) ] div [ _fieldRow ] [ div [ _inputField ] [ - label [ _for "name" ] [ locStr s["Church Name"] ] - input [ _type "text"; _name (nameof model.Name); _id "name"; _required; _autofocus; _value model.Name ] + label [ _for (nameof model.Name) ] [ locStr s["Church Name"] ] + inputField "text" (nameof model.Name) model.Name [ _required; _autofocus ] ] div [ _inputField ] [ - label [ _for "City"] [ locStr s["City"] ] - input [ _type "text"; _name (nameof model.City); _id "city"; _required; _value model.City ] + label [ _for (nameof model.City) ] [ locStr s["City"] ] + inputField "text" (nameof model.City) model.City [ _required ] ] div [ _inputField ] [ - label [ _for "state" ] [ locStr s["State or Province"] ] - input [ _type "text" - _name (nameof model.State) - _id "state" - _minlength "2"; _maxlength "2" - _value model.State - _required ] + label [ _for (nameof model.State) ] [ locStr s["State or Province"] ] + inputField "text" (nameof model.State) model.State [ _minlength "2"; _maxlength "2"; _required ] ] ] div [ _fieldRow ] [ div [ _checkboxField ] [ - input [ _type "checkbox" - _name (nameof model.HasInterface) - _id "hasInterface" - _value "True" - if defaultArg model.HasInterface false then _checked ] - label [ _for "hasInterface" ] [ locStr s["Has an interface with Virtual Prayer Room"] ] + inputField "checkbox" (nameof model.HasInterface) "True" + [ if defaultArg model.HasInterface false then _checked ] + label [ _for (nameof model.HasInterface) ] [ locStr s["Has an interface with Virtual Prayer Room"] ] ] ] div [ _fieldRowWith [ "pt-fadeable" ]; _id "divInterfaceAddress" ] [ div [ _inputField ] [ - label [ _for "interfaceAddress" ] [ locStr s["VPR Interface URL"] ] - input [ _type "url" - _name (nameof model.InterfaceAddress) - _id "interfaceAddress"; - _value (defaultArg model.InterfaceAddress "") ] + label [ _for (nameof model.InterfaceAddress) ] [ locStr s["VPR Interface URL"] ] + inputField "url" (nameof model.InterfaceAddress) (defaultArg model.InterfaceAddress "") [] ] ] div [ _fieldRow ] [ submit [] "save" s["Save Church"] ] @@ -73,17 +62,7 @@ let maintain (churches : Church list) (stats : Map) ctx vi | [] -> space | _ -> table [ _class "pt-table pt-action-table" ] [ - thead [] [ - tr [] [ - th [] [ locStr s["Actions"] ] - th [] [ locStr s["Name"] ] - th [] [ locStr s["Location"] ] - th [] [ locStr s["Groups"] ] - th [] [ locStr s["Requests"] ] - th [] [ locStr s["Users"] ] - th [] [ locStr s["Interface?"] ] - ] - ] + tableHeadings s [ "Actions"; "Name"; "Location"; "Groups"; "Requests"; "Users"; "Interface?" ] churches |> List.map (fun ch -> let chId = flatGuid ch.churchId diff --git a/src/PrayerTracker.UI/CommonFunctions.fs b/src/PrayerTracker.UI/CommonFunctions.fs index 40a3d1d..0f00478 100644 --- a/src/PrayerTracker.UI/CommonFunctions.fs +++ b/src/PrayerTracker.UI/CommonFunctions.fs @@ -138,6 +138,21 @@ let _inputField = _inputFieldWith [] /// The class that designates a checkbox / label pair let _checkboxField = _class "pt-checkbox-field" +/// Create an input field of the given type, with matching name and ID and the given value +let inputField typ name value attrs = + List.concat [ [ _type typ; _name name; _id name; if value <> "" then _value value ]; attrs ] |> input + +/// Generate a table heading with the given localized column names +let tableHeadings (s : IStringLocalizer) (headings : string list) = + headings + |> List.map (fun heading -> th [ _scope "col" ] [ locStr s[heading] ]) + |> tr [] + |> List.singleton + |> thead [] + +/// For a list of strings, prepend a pound sign and string them together with commas (CSS selector by ID) +let toHtmlIds it = it |> List.map (fun x -> $"#%s{x}") |> String.concat ", " + /// The name this function used to have when the view engine was part of Giraffe let renderHtmlNode = RenderView.AsString.htmlNode diff --git a/src/PrayerTracker.UI/Layout.fs b/src/PrayerTracker.UI/Layout.fs index e17d9d6..b303bf3 100644 --- a/src/PrayerTracker.UI/Layout.fs +++ b/src/PrayerTracker.UI/Layout.fs @@ -198,13 +198,13 @@ let private commonHead = [ ] /// Render the portion of the page -let private htmlHead m pageTitle = +let private htmlHead viewInfo pgTitle = let s = I18N.localizer.Force () head [] [ meta [ _charset "UTF-8" ] - title [] [ locStr pageTitle; titleSep; locStr s["PrayerTracker"] ] + title [] [ locStr pgTitle; titleSep; locStr s["PrayerTracker"] ] yield! commonHead - for cssFile in m.Style do + for cssFile in viewInfo.Style do link [ _rel "stylesheet"; _href $"/css/{cssFile}.css"; _type "text/css" ] ] @@ -224,16 +224,18 @@ let private helpLink link = ] /// Render the page title, and optionally a help link -let private renderPageTitle m pageTitle = +let private renderPageTitle viewInfo pgTitle = h2 [ _id "pt-page-title" ] [ - match m.HelpLink with Some link -> PrayerTracker.Utils.Help.fullLink (langCode ()) link |> helpLink | None -> () - locStr pageTitle + match viewInfo.HelpLink with + | Some link -> PrayerTracker.Utils.Help.fullLink (langCode ()) link |> helpLink + | None -> () + locStr pgTitle ] /// Render the messages that may need to be displayed to the user -let private messages m = +let private messages viewInfo = let s = I18N.localizer.Force () - m.Messages + viewInfo.Messages |> List.map (fun msg -> table [ _class $"pt-msg {MessageLevel.toCssClass msg.Level}" ] [ tr [] [ @@ -257,10 +259,10 @@ let private messages m = open System /// Render the