From 5cfe8848fe14490e09c91ee5dc19eebbff2949b8 Mon Sep 17 00:00:00 2001
From: LukeTemp <38944835+LukeTemp@users.noreply.github.com>
Date: Tue, 27 Feb 2024 14:11:30 +0000
Subject: [PATCH] Upstream helper functions to Yesod.Form.Option. (#1828)
The 2 new functions are:
- `optionsFromList'`: Creates an `OptionList` from a list using the `PathPiece` instance for the external value
and a custom function for the user-facing value.
- `optionsEnum'`: creates an `OptionList` from an enumeration.
---
yesod-form/ChangeLog.md | 7 +++
yesod-form/Yesod/Form/Option.hs | 96 +++++++++++++++++++++++++++++++++
yesod-form/yesod-form.cabal | 3 +-
3 files changed, 105 insertions(+), 1 deletion(-)
create mode 100644 yesod-form/Yesod/Form/Option.hs
diff --git a/yesod-form/ChangeLog.md b/yesod-form/ChangeLog.md
index 6fa84b00b..7cbbf4518 100644
--- a/yesod-form/ChangeLog.md
+++ b/yesod-form/ChangeLog.md
@@ -1,5 +1,12 @@
# ChangeLog for yesod-form
+
+## 1.7.7
+
+* Added `optionsFromList'` to create an OptionList from a List, using the PathPiece instance for the external value and
+a custom function for the user-facing value. Also added `optionsEnum'` to create an OptionList from an enumeration
+[#1828](https://github.com/yesodweb/yesod/pull/1828)
+
## 1.7.6
* Added `datetimeLocalField` for creating a html `` [#1817](https://github.com/yesodweb/yesod/pull/1817)
diff --git a/yesod-form/Yesod/Form/Option.hs b/yesod-form/Yesod/Form/Option.hs
new file mode 100644
index 000000000..fcbd9dc0b
--- /dev/null
+++ b/yesod-form/Yesod/Form/Option.hs
@@ -0,0 +1,96 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+module Yesod.Form.Option where
+
+import Yesod.Core
+import Yesod.Form.Fields
+
+-- | Creates an `OptionList` from a `List`, using the `PathPiece` instance for
+-- the external value and a custom function for the user-facing value.
+--
+-- @since 1.7.7
+--
+-- PathPiece instances should provide suitable external values, since path
+-- pieces serve to be exposed through URLs or HTML anyway. Show/Read instances
+-- are avoided here since they could leak internal representations to forms,
+-- query params, javascript etc.
+--
+-- === __Example usage__
+--
+-- > data UserRole = URSalesTeam | URSalesHead | URTechTeam | URTechHead
+-- >
+-- > instance PathPiece UserDepartment where
+-- > toPathPiece = \case
+-- > URSalesTeam -> "sales-team"
+-- > URSalesHead -> "sales-head"
+-- > URTechTeam -> "tech-team"
+-- > URTechHead -> "tech-head"
+-- > fromPathPiece = \case
+-- > "sales-team" -> Just URSalesTeam
+-- > "sales-head" -> Just URSalesHead
+-- > "tech-team" -> Just URTechTeam
+-- > "tech-head" -> Just URTechHead
+-- > _ -> Nothing
+-- >
+-- > userRoleOptions ::
+-- > (MonadHandler m, RenderMessage (HandlerSite m) msg) => m (OptionList UserRole)
+-- > userRoleOptions = optionsFromList' userRoles toMsg
+-- > where
+-- > userRoles = [URSalesTeam, URSalesHead, URTechTeam, URTechHead]
+-- > toMsg :: UserRole -> Text
+-- > toMsg = \case
+-- > URSalesTeam -> "Sales Team"
+-- > URSalesHead -> "Head of Sales Team"
+-- > URTechTeam -> "Tech Team"
+-- > URTechHead -> "Head of Tech Team"
+--
+-- userRoleOptions, will produce an OptionList with the following attributes:
+--
+-- > +----------------+----------------+--------------------+
+-- > | Internal Value | External Value | User-facing Value |
+-- > +----------------+----------------+--------------------+
+-- > | URSalesTeam | sales-team | Sales Team |
+-- > +----------------+----------------+--------------------+
+-- > | URSalesHead | sales-head | Head of Sales Team |
+-- > +----------------+----------------+--------------------+
+-- > | URTechTeam | tech-team | Tech Team |
+-- > +----------------+----------------+--------------------+
+-- > | URTechHead | tech-head | Head of Tech Team |
+-- > +----------------+----------------+--------------------+
+--
+-- Note that the type constraint allows localizable messages in place of toMsg (see
+-- https://en.wikipedia.org/wiki/Yesod_(web_framework)#Localizable_messages).
+
+optionsFromList' ::
+ MonadHandler m
+ => RenderMessage (HandlerSite m) msg
+ => PathPiece a
+ => [a]
+ -> (a -> msg)
+ -> m (OptionList a)
+optionsFromList' lst toDisplay = do
+ mr <- getMessageRender
+ pure $ mkOptionList $ flip map lst $ \v -> Option
+ { optionDisplay = mr $ toDisplay v
+ , optionInternalValue = v
+ , optionExternalValue = toPathPiece v
+ }
+
+-- | Creates an `OptionList` from an `Enum`.
+--
+-- @since 1.7.7
+--
+-- optionsEnum' == optionsFromList' [minBound..maxBound]
+--
+-- Creates an `OptionList` containing every constructor of `a`, so that these
+-- constructors do not need to be typed out. Bounded and Enum instances must
+-- exist for `a` to use this.
+optionsEnum' ::
+ MonadHandler m
+ => RenderMessage (HandlerSite m) msg
+ => PathPiece a
+ => Enum a
+ => Bounded a
+ => (a -> msg)
+ -> m (OptionList a)
+optionsEnum' = optionsFromList' [minBound..maxBound]
diff --git a/yesod-form/yesod-form.cabal b/yesod-form/yesod-form.cabal
index d0bcfcb43..c7d9b45c8 100644
--- a/yesod-form/yesod-form.cabal
+++ b/yesod-form/yesod-form.cabal
@@ -1,6 +1,6 @@
cabal-version: >= 1.10
name: yesod-form
-version: 1.7.6
+version: 1.7.7
license: MIT
license-file: LICENSE
author: Michael Snoyman
@@ -46,6 +46,7 @@ library
build-depends: network-uri >= 2.6
exposed-modules: Yesod.Form
+ Yesod.Form.Option
Yesod.Form.Types
Yesod.Form.Functions
Yesod.Form.Bootstrap3