[adde Reform tutorial Jeremy Shaw **20120521194741 Ignore-this: aee266b107c9b5fea036283c892f63f9 ] hunk ./Makefile 5 -DEPS := HelloWorld.lhs RouteFilters.lhs Templates.lhs RqData.lhs FileServing.lhs WebRoutes.lhs AcidState.lhs TemplateHaskell.lhs -HELLOWORLD_DEMOS := HelloWorld.lhs +DEPS := HelloWorld.lhs RouteFilters.lhs Templates.lhs RqData.lhs FileServing.lhs Reform.markdown.lhs WebRoutes.lhs AcidState.lhs TemplateHaskell.lhs +HELLOWORLD_DEMOS := HelloWorld.lhs hunk ./Makefile 8 -RQDATA_DEPS := RqDataLimiting.lhs RqDataParsing.lhs Cookies.lhs -RQDATA_DEMOS := HelloRqData.lhs RqDataPost.lhs RqDataError.lhs RqDataUpload.lhs RqDataRead.lhs RqDataCheck.lhs RqDataCheckOther.lhs RqDataOptional.lhs -COOKIE_DEPS := CookieLife.lhs CookieIssues.lhs CookieFeatures.lhs -COOKIE_DEMOS := CookieCounter.lhs -FILESERVING_DEMOS := FileServingDirectory.lhs FileServingSingle.lhs -ACIDSTATE_DEMOS := AcidStateCounter.lhs IxSet.lhs IxSetDataLens.lhs AcidStateAdvanced.lhs -WEBROUTES_DEMOS := WebRoutesDemo.lhs WebRoutesBoomerang.lhs WebRoutesHSP.lhs -TEMPLATES_DEPS := -TEMPLATES_DEMOS := HelloBlaze.lhs TemplatesHeist.lhs TemplatesHSP.lhs JMacro.lhs TemplatesHSPI18n.lhs -DEMOS := $(RQDATA_DEMOS) $(ROUTE_FILTER_DEMOS) $(HELLOWORLD_DEMOS) $(TEMPLATES_DEPS) $(TEMPLATES_DEMOS) $(COOKIE_DEMOS) $(FILESERVING_DEMOS) $(ACIDSTATE_DEMOS) $(WEBROUTES_DEMOS) -EXTRA_DEPS := Makefile +RQDATA_DEPS := RqDataLimiting.lhs RqDataParsing.lhs Cookies.lhs +RQDATA_DEMOS := HelloRqData.lhs RqDataPost.lhs RqDataError.lhs RqDataUpload.lhs RqDataRead.lhs RqDataCheck.lhs RqDataCheckOther.lhs RqDataOptional.lhs +COOKIE_DEPS := CookieLife.lhs CookieIssues.lhs CookieFeatures.lhs +COOKIE_DEMOS := CookieCounter.lhs +FILESERVING_DEMOS := FileServingDirectory.lhs FileServingSingle.lhs +REFORM_DEMOS := +ACIDSTATE_DEMOS := AcidStateCounter.lhs IxSet.lhs IxSetDataLens.lhs AcidStateAdvanced.lhs +WEBROUTES_DEMOS := WebRoutesDemo.lhs WebRoutesBoomerang.lhs WebRoutesHSP.lhs +TEMPLATES_DEPS := +TEMPLATES_DEMOS := HelloBlaze.lhs TemplatesHeist.lhs TemplatesHSP.lhs JMacro.lhs TemplatesHSPI18n.lhs +DEMOS := $(RQDATA_DEMOS) $(ROUTE_FILTER_DEMOS) $(HELLOWORLD_DEMOS) $(TEMPLATES_DEPS) $(TEMPLATES_DEMOS) $(COOKIE_DEMOS) $(FILESERVING_DEMOS) $(REFORM_DEMOS) $(ACIDSTATE_DEMOS) $(WEBROUTES_DEMOS) +EXTRA_DEPS := Makefile hunk ./Makefile 23 -all: $(DESTDIR) $(subst .lhs,.hs,$(addprefix $(DESTDIR),$(DEMOS))) $(addprefix $(DESTDIR),$(CSS)) $(subst .lhs,.html,$(addprefix $(DESTDIR),$(DEPS))) $(DESTDIR)$(BASENAME).html html/theme/theme.css html/theme/code-background.jpg html/factorial.tpl +all: $(DESTDIR) $(subst .lhs,.hs,$(addprefix $(DESTDIR),$(DEMOS))) $(addprefix $(DESTDIR),$(CSS)) $(subst .lhs,.html,$(subst .markdown.lhs,.html,$(addprefix $(DESTDIR),$(DEPS)))) $(DESTDIR)$(BASENAME).html html/theme/theme.css html/theme/code-background.jpg html/factorial.tpl $(DESTDIR)Reform.html $(DESTDIR)Reform.hs hunk ./Makefile 32 +Reform.html : Reform.markdown.lhs hunk ./Makefile 38 -%.lhs : %.markdown.lhs - markdown --html4tags $< > $@ +%.html : %.markdown.lhs + cpphs --noline -DHsColour $< | sed 's/^] /> /' | ./HsColour -css -partial -lit | markdown --html4tags > $@ hunk ./Makefile 41 -html/theme/theme.css: theme.css +html/theme/theme.css: theme.css hunk ./Makefile 56 -toc.html: $(subst .lhs,.html,$(DEPS)) gen-toc.hs +toc.html: $(subst .lhs,.html,$(subst .markdown.lhs,.html,$(DEPS))) gen-toc.hs hunk ./Makefile 108 +$(DESTDIR)%.hs: %.markdown.lhs + cpphs --noline $^ | grep -v '{-# LANGUAGE CPP #-}' | sed -n 's/^> \?//p' > $@ + hunk ./Makefile 116 + + addfile ./Reform.markdown.lhs hunk ./Reform.markdown.lhs 1 + + + + Crash Course in Happstack + + + + hunk ./Reform.markdown.lhs 11 +

Reform Tutorial

+ +`reform` is a library for creating type-safe, composable, and +validated HTML forms. It is built around applicative functors and is +based on the same principles as `formlets` and `digestive-functors <= +0.2`. + +The core `reform` library is designed to be portable and can be used +with a wide variety of Haskell web frameworks and template solutions +-- though only a few options are supported at the moment. + +The most basic method of creating and processing forms with out the +assistance of `reform` is to: + + 1. create a `
` tag with the desired elements by hand + + 2. write code which processes the form data set and tries to extract a value from it + +The developer will encounter a number of difficulties using this method: + + 1. the developer must be careful to use the same `name` field in the + HTML and the code. + + 2. if a new field is added to the form, the code must be manually + updated. Failure to do so will result in the new field being + silently ignored. + + 3. form fragments can not be easily combined because the `name` or +`id` fields might collide. Additionally, there is no simple way to +combine the validation/value extraction code. + + 4. if the form fails to validate, it is difficult to redisplay the + form with the error messages and data that was submitted. + +`reform` solves these problems by combining the view generation code +and validation code into a single `Form` element. The `Form` elements +can be safely combined to create more complex forms. + +In theory, `reform` could be applied to other domains, such as +command-line or GUI applications. However, `reform` is based around +the pattern of: + + 1. generate the entire form at once + 2. wait until the user has filled out all the fields and submitted it + 3. process the results and generate an answer or redisplay the form with validation errors + +For most interactive applications, there is no reason to wait until +the entire form has been filled out to perform validation. + +

Brief History

+ +`reform` is an extension of the OCaml-based `formlets` concept +originally developed by Ezra Cooper, Sam Lindley, Philip Wadler and +Jeremy Yallop. The original `formlets` code was ported to Haskell as the +`formlets` library, and then revamped again as the +`digestive-functors <= 0.2` library. + +`digestive-functors` 0.3 represents a major break from the traditional +`formlets` model. The primary motivation behind `digestive-functors` +0.3 was (mostly likely) to allow the separation of validators from the +view code. This allows library authors to define validation for forms, +while allowing the library users to create the view for the forms. It also +provides a mechanism to support templating systems like `Heist`, where +the view is defined in an external XML file rather than Haskell code. + +In order to achieve this, `digestive-functors` 0.3 unlinks the +validation and view code and requires the developers to stitch them +back together using `String` based names. This, of course, leads to +runtime errors. If the library author adds new required fields to the +validator, the user gets no compile time warnings or errors to let +them know their code is broken. + +The `Reform` library is a heavily modified fork of +`digestive-functors` 0.2. It builds on the the traditional `formlets` +safety and style and extends it to allow view and validation +separation in a type-safe manner. + +You can find the original papers on `formlets` [here](http://groups.inf.ed.ac.uk/links/formlets/). + +

Hello Form!

+ + +The easiest way to learn `Reform` is through example. We will start +with a simple form that does not require any special validation. We +will then extend the form, adding some simple validators. And then we +will show how we can split the validation and view for our form into +separate libraries. + +This example uses Happstack for the web server and HSP for the templating library. + +First we have some pragmas: +
+ +> {-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses +> , ScopedTypeVariables, TypeFamilies, TypeSynonymInstances #-} +> {-# OPTIONS_GHC -F -pgmFtrhsx #-} +> module Main where +> + +
+ +And then some imports. We import modules from three different `reform` packages: the core `reform` library, the `reform-happstack` package, and the `reform-hsp` package: + +
+ +> import Control.Applicative +> import Control.Applicative.Indexed (IndexedFunctor(..), IndexedApplicative(..)) +> import Control.Monad (msum) +> import Happstack.Server +> import Happstack.Server.HSP.HTML () +> import HSP.ServerPartT +> import HSP +> import Text.Reform ( CommonFormError(..), Form, FormError(..), Proof(..), (++>) +> , (<++), commonFormErrorStr, decimal, prove, transformEither, transform) +> import Text.Reform.Happstack +> import Text.Reform.HSP.String +> + +
+ +Next we will create a type alias for our application's server monad: + +
+ +> type AppT m = XMLGenT (ServerPartT m) +> + +
+ +We will also want a function that generates a page template for our app: + +
+ +> appTemplate :: ( Functor m, Monad m +> , EmbedAsChild (ServerPartT m) headers +> , EmbedAsChild (ServerPartT m) body +> ) => +> String -- ^ contents of tag +> -> headers -- ^ extra content for <head> tag, use () for nothing +> -> body -- ^ contents of <body> tag +> -> AppT m Response +> appTemplate title headers body = +> toResponse <$> +> <html> +> <head> +> <title><% title %> +> <% headers %> +> +> +> <% body %> +> +> +> + +
+ +Forms have the type `Form` which looks like: + +
+ +] newtype Form m input error view proof a = Form { ... } + +
+ +As you will note it is heavily parameterized: + +
+
m
a monad which can be used to validate the result
+
input
the framework specific type containing the fields from the form data set.
+
error
An application specific type for form validation errors.
+
view
The type of the view for the form.
+
proof
A datatype which names something that has been proven about the result.
+
a
The value returned when the form data set is successfully decoded and validated.
+
+ +In order to keep our type signatures sane, it is convenient to create an application specific type alias for the `Form` type: + +
+ +> type SimpleForm = Form (AppT IO) [Input] AppError [AppT IO (XMLType (ServerPartT IO))] () +> + +
+ +`AppError` is an application specific type used to report form validation errors: + +
+ +> data AppError +> = Required +> | NotANatural String +> | AppCFE (CommonFormError [Input]) +> deriving Show +> + +
+ +Instead of have one error type for all the forms, we could have per-form error types -- or even just use `String`. The advantage of using a type is that it makes it easier to provide I18N translations, or for users of a library to customize the text of the error messages. The disadvantage of using a custom type over a plain `String` is that it can make it more difficult to combine forms into larger forms since they must all have the same error type. Additionally, it is a bit more work to create the error type and the `FormError` instance. + +We will want an `EmbedAsChild` instance so that we can easily embed the errors in our HTML: + +
+ +> instance (Monad m) => EmbedAsChild (ServerPartT m) AppError where +> asChild Required = asChild $ "required" +> asChild (NotANatural str) = asChild $ "Could not decode as a positive integer: " ++ str +> asChild (AppCFE cfe) = asChild $ commonFormErrorStr show cfe +> + +
+ +The error type also needs a `FormError` instance: + +
+ +> instance FormError AppError where +> type ErrorInputType AppError = [Input] +> commonFormError = AppCFE +> + +
+ +Internally, `reform` has an error type `CommonFormError` which is used +to report things like missing fields and other internal errors. The +`FormError` class is used to lift those errors into our custom error +type. + +Now we have the groundwork laid to create a simple form. Let's create +a form that allows users to post a message. First we will want a type to +represent the message -- a simple record will do: + +
+ +> data Message = Message +> { name :: String -- ^ the author's name +> , title :: String -- ^ the message title +> , message :: String -- ^ contents of the message +> } deriving (Eq, Ord, Read, Show) +> + +
+ +and a simple function to render the `Message` as `XML`: + +
+ +> renderMessage :: (Monad m) => Message -> AppT m XML +> renderMessage msg = +>
+>
name:
<% name msg %>
+>
title:
<% title msg %>
+>
message:
<% message msg %>
+>
+> + +
+ +Now we can create a very basic form: + +
+ +> postForm :: SimpleForm Message +> postForm = +> Message +> <$> label "name:" ++> inputText "" <++ br +> <*> label "title: " ++> inputText "" <++ br +> <*> (label "message:" <++ br) ++> textarea 80 40 "" <++ br +> <* inputSubmit "post" +> + +
+ +This form contains all the information needed to generate the form elements and to parse the submitted form data set and extract a `Message` value. + +The following functions come from `reform-hsp`. `reform-blaze` provides similar functions. + + * `label` function creates a `