| Safe Haskell | Safe-Inferred |
|---|---|
| Language | GHC2021 |
DevForms
Description
DevForms lets you define multi-page survey forms using a concise monadic builder DSL. Forms are served via a built-in web server (Scotty), and submissions are stored as JSONL files.
Each question type comes in two variants:
- A plain version (e.g.
questionLikert) that uses default options. - A
Withversion (e.g.questionLikertWith) that accepts a builder block for configuring options likesetOptionalor bounds.
Example
main :: IO ()
main = devFormServer 9000 $ do
form "Animal Survey" "animals" $ do
questionLikert "I enjoy seeing animals"
questionChoice "Favourite animal"
[Alpaca, Bumblebee, Camel, Duck]
questionDateWith "When would you like to visit the zoo?" $ setOptional
questionIntegerWith "How many tickets?" $ do
setLowerBoundInclusive 1
setUpperBoundInclusive 10
Synopsis
- type ServerBuilder = Writer (Endo Server)
- type FormBuilder = Writer (Endo Form)
- data QuestionBuilder a
- data IntegerQuestionBuilder a
- class HasOptional m where
- setOptional :: m
- class HasBounds m where
- setLowerBoundInclusive :: Integer -> m
- setUpperBoundInclusive :: Integer -> m
- devFormServer :: Int -> ServerBuilder () -> IO ()
- form :: Text -> Text -> FormBuilder () -> ServerBuilder ()
- questionCheckbox :: Text -> FormBuilder ()
- questionCheckboxWith :: Text -> QuestionBuilder () -> FormBuilder ()
- questionLikert :: Text -> FormBuilder ()
- questionLikertWith :: Text -> QuestionBuilder () -> FormBuilder ()
- questionChoice :: Text -> [Text] -> FormBuilder ()
- questionChoiceWith :: Text -> [Text] -> QuestionBuilder () -> FormBuilder ()
- questionDate :: Text -> FormBuilder ()
- questionDateWith :: Text -> QuestionBuilder () -> FormBuilder ()
- questionTime :: Text -> FormBuilder ()
- questionTimeWith :: Text -> QuestionBuilder () -> FormBuilder ()
- questionInteger :: Text -> FormBuilder ()
- questionIntegerWith :: Text -> IntegerQuestionBuilder () -> FormBuilder ()
- questionFreeText :: Text -> FormBuilder ()
- questionFreeTextWith :: Text -> QuestionBuilder () -> FormBuilder ()
- questionRegexText :: Text -> Text -> FormBuilder ()
- questionRegexTextWith :: Text -> Text -> QuestionBuilder () -> FormBuilder ()
Documentation
type ServerBuilder = Writer (Endo Server) Source #
A builder monad for configuring the devforms server. Use form to add forms.
type FormBuilder = Writer (Endo Form) Source #
A builder monad for adding questions to a form. Use the question* functions to add questions.
data QuestionBuilder a Source #
A builder monad for configuring shared question options (e.g. optionality).
Instances
| Applicative QuestionBuilder Source # | |
Defined in Question Methods pure :: a -> QuestionBuilder a # (<*>) :: QuestionBuilder (a -> b) -> QuestionBuilder a -> QuestionBuilder b # liftA2 :: (a -> b -> c) -> QuestionBuilder a -> QuestionBuilder b -> QuestionBuilder c # (*>) :: QuestionBuilder a -> QuestionBuilder b -> QuestionBuilder b # (<*) :: QuestionBuilder a -> QuestionBuilder b -> QuestionBuilder a # | |
| Functor QuestionBuilder Source # | |
Defined in Question Methods fmap :: (a -> b) -> QuestionBuilder a -> QuestionBuilder b # (<$) :: a -> QuestionBuilder b -> QuestionBuilder a # | |
| Monad QuestionBuilder Source # | |
Defined in Question Methods (>>=) :: QuestionBuilder a -> (a -> QuestionBuilder b) -> QuestionBuilder b # (>>) :: QuestionBuilder a -> QuestionBuilder b -> QuestionBuilder b # return :: a -> QuestionBuilder a # | |
| HasOptional (QuestionBuilder ()) Source # | |
Defined in Question Methods setOptional :: QuestionBuilder () Source # | |
data IntegerQuestionBuilder a Source #
A builder monad for configuring integer question options (bounds and optionality).
Instances
class HasOptional m where Source #
Typeclass for builders that support marking a question as optional.
Methods
setOptional :: m Source #
Instances
| HasOptional (IntegerQuestionBuilder ()) Source # | |
Defined in Question Methods | |
| HasOptional (QuestionBuilder ()) Source # | |
Defined in Question Methods setOptional :: QuestionBuilder () Source # | |
class HasBounds m where Source #
Typeclass for builders that support setting numeric bounds.
Methods
setLowerBoundInclusive :: Integer -> m Source #
setUpperBoundInclusive :: Integer -> m Source #
Instances
| HasBounds (IntegerQuestionBuilder ()) Source # | |
Defined in Question Methods setLowerBoundInclusive :: Integer -> IntegerQuestionBuilder () Source # setUpperBoundInclusive :: Integer -> IntegerQuestionBuilder () Source # | |
devFormServer :: Int -> ServerBuilder () -> IO () Source #
Start the devforms web server on the given port.
This is the top-level entry point for a devforms application. The second
argument is a ServerBuilder block in which you define one or more forms
using form. The server provides:
- Individual form pages with client-side validation
- A submission endpoint that stores answers in
answers-<formId>.jsonl
form :: Text -> Text -> FormBuilder () -> ServerBuilder () Source #
Define a survey form.
The first argument is the human-readable title displayed at the top of the form page. The second argument is a form identifier used for:
- URL routing — the form is served at
form<formId> - Persistent storage — submissions are appended to
answers-<formId>.jsonl
The third argument is a FormBuilder block where you add questions using
the question* functions.
questionCheckbox :: Text -> FormBuilder () Source #
Add a yes/no checkbox question with default options. Renders as a single checkbox that the respondent can tick or leave unticked.
questionCheckboxWith :: Text -> QuestionBuilder () -> FormBuilder () Source #
Add a yes/no checkbox question with custom options. Renders as a single checkbox that the respondent can tick or leave unticked.
The second argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
questionLikert :: Text -> FormBuilder () Source #
Add a Likert-scale question with default options. Renders as a 5-point agreement scale (Strongly disagree … Strongly agree) plus a "Cannot say" option.
questionLikertWith :: Text -> QuestionBuilder () -> FormBuilder () Source #
Add a Likert-scale question with custom options. Renders as a 5-point agreement scale (Strongly disagree … Strongly agree) plus a "Cannot say" option.
The second argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
questionChoice :: Text -> [Text] -> FormBuilder () Source #
Add a multiple-choice question with default options. Renders as a group of radio buttons — the respondent must select exactly one of the provided options.
The first argument is the question label; the second is the list of choices.
questionChoiceWith :: Text -> [Text] -> QuestionBuilder () -> FormBuilder () Source #
Add a multiple-choice question with custom options. Renders as a group of radio buttons — the respondent must select exactly one of the provided options.
The first argument is the question label; the second is the list of choices.
The third argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
questionDate :: Text -> FormBuilder () Source #
Add a date-picker question with default options. Renders as an HTML date
input and stores the answer in YYYY-MM-DD format.
questionDateWith :: Text -> QuestionBuilder () -> FormBuilder () Source #
Add a date-picker question with custom options. Renders as an HTML date
input and stores the answer in YYYY-MM-DD format.
The second argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
questionTime :: Text -> FormBuilder () Source #
Add a time-picker question with default options. Renders as an HTML time
input and stores the answer in HH:MM format.
questionTimeWith :: Text -> QuestionBuilder () -> FormBuilder () Source #
Add a time-picker question with custom options. Renders as an HTML time
input and stores the answer in HH:MM format.
The second argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
questionInteger :: Text -> FormBuilder () Source #
Add an integer input question with default options (no bounds).
questionIntegerWith :: Text -> IntegerQuestionBuilder () -> FormBuilder () Source #
Add an integer input question with custom options. The second argument is
an IntegerQuestionBuilder block where you can configure bounds using
setLowerBoundInclusive and setUpperBoundInclusive, as well as shared
options like setOptional. Bounds are enforced both client-side (via HTML
attributes) and server-side on submission.
Example
questionIntegerWith "How many pets do you have?" $ do
setLowerBoundInclusive 0
setUpperBoundInclusive 50
setOptional
questionFreeText :: Text -> FormBuilder () Source #
Add a free-text textarea question with default options. The respondent can enter arbitrary text.
questionFreeTextWith :: Text -> QuestionBuilder () -> FormBuilder () Source #
Add a free-text textarea question with custom options. The respondent can enter arbitrary text.
The second argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
questionRegexText :: Text -> Text -> FormBuilder () Source #
Add a regex-validated text input question with default options. The respondent's answer must match the given POSIX extended regex pattern.
The first argument is the question label; the second is the regex pattern.
questionRegexTextWith :: Text -> Text -> QuestionBuilder () -> FormBuilder () Source #
Add a regex-validated text input question with custom options. The
respondent's answer must match the given POSIX extended regex pattern. The
pattern is also set as the HTML pattern attribute for client-side
validation.
The first argument is the question label; the second is the regex pattern.
The third argument is a QuestionBuilder block where you can configure
shared options such as setOptional.
Example
questionRegexTextWith "SemVer number" "^v\d+.\d+.\d+$" $ setOptional