devforms
Safe HaskellSafe-Inferred
LanguageGHC2021

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:

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

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

Instances details
Applicative QuestionBuilder Source # 
Instance details

Defined in Question

Functor QuestionBuilder Source # 
Instance details

Defined in Question

Methods

fmap :: (a -> b) -> QuestionBuilder a -> QuestionBuilder b #

(<$) :: a -> QuestionBuilder b -> QuestionBuilder a #

Monad QuestionBuilder Source # 
Instance details

Defined in Question

HasOptional (QuestionBuilder ()) Source # 
Instance details

Defined in Question

data IntegerQuestionBuilder a Source #

A builder monad for configuring integer question options (bounds and optionality).

class HasOptional m where Source #

Typeclass for builders that support marking a question as optional.

Methods

setOptional :: m Source #

Instances

Instances details
HasOptional (IntegerQuestionBuilder ()) Source # 
Instance details

Defined in Question

HasOptional (QuestionBuilder ()) Source # 
Instance details

Defined in Question

class HasBounds m where Source #

Typeclass for builders that support setting numeric bounds.

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