Below is a sample form app that you can try out. The blue circles to the right indicate progress and turn red when entered text is not valid. The next-page button (labeled "Volgende") is enabled once the page has been completely filled out. The entire form is specified in 115 lines of Haskell (source) and about 100 lines of css code (source) for describing table borders and colors. If your browser does not handle the iframe well, you can open a non-embedded version.
For simplicity, the form has been implemented as a single instance, which means that all users will view and edit the same form. For a more serious application, a multi-user form can be easily implemented using the built-in user-management functionality.
The source code is straightforward Haskell code that constructs the form using the following functions (also called combinators):
form :: [FormPage] -> WebForm page :: [FormElt] -> FormPage tableElt :: String -> [[FormElt]] -> FormElt htmlElt :: String -> FormElt textAnswerElt :: QuestionTag -> FormElt radioAnswerElt :: QuestionTag -> [String] -> FormElt buttonAnswerElt :: QuestionTag -> [String] -> FormElt radioTextAnswerElt :: QuestionTag -> QuestionTag -> [String] -> FormElt styleElt :: String -> FormElt -> FormElt
The root of the form is a definition mainForm = form [page_1, page_2 ... page_n] that puts the pages of the form together. Each page takes a list of FormElts, which are either an html fragment, an answer widget, or a nested table whose cells are again FormElts. Html fragments are constructed with htmlElt, and answer widgets are constructed with textAnswerElt, radioAnswerElt, or buttonAnswerElt. Each of the answer elements has a string tag that determines its column in a csv file containing the results. The hybrid combinator RadioTextAnswerElt is a radio widget that has a textual alternative at the bottom. It has two tags: one for the radio answer and one for the text (which may be empty). In order to style the form, css styles can be provided with the styleElt combinator, or put in a separate css file, since the tables have a tag that is added to their html class.
For textual answers, there is an special combinator that supports validation of the answer:
textAnswerValidateElt :: QuestionTag -> (String -> Bool) -> FormElt
Because the form description is actually a Haskell program, common patterns can be expressed using functions. For example, we can create a question that has numbered buttons as its possible answers with the following function:
mkScaleQuestion :: Int -> String -> String -> TableRow mkScaleQuestion n tag question = [ htmlElt question, buttonAnswerElt tag $ map show [1..n] ]
Now we can use mkScaleQuestion 10 "moeilijk" "Ik vond het moeilijk om te kiezen" to get the following line in the form:
Another example of this abstraction are the vignettes on page 5 and 6 of the example form. The structure as well as most text on these pages is constant. Instead of building each page by hand, we can define a function mkVignettePage (see source), which takes a record that contains only those part that are different and constructs a form page for it. This not only makes it much easier to add or modify vignette pages, but also makes it possible to restyle all of them in a single place. Moreover, it guarantees that all vignettes have exactly the same format.