Introduction

Formlets are a way of building HTML forms that are type-safe, handle errors, abstract and are easy to combine into bigger forms. Here's an example:

name::FormStringname=inputNothing

The input function takes a Maybe String, and produces a XHtmlForm String. The Maybe String is used for default values. If you give it a nothing, it won't have a default value. If you pass in a (Just "value"), it will be pre-populated with the value "value".

You can easily combine formlets using the Applicative Functor combinators. Suppose you have a User-datatype:

dataUser=User{name::String,age::Integer,email::String}

You can then build a form that produces a user:

userForm::FormUseruserForm=User<$>name<*>inputInteger<*>inputNothing

You can also have more advanced widgets, like a radio-choice, that's where you use enumRadio:

enumRadio::(Monadm,Enuma)=>[(a,String)]->Maybea->Forma

So it asks for a list of pairs with a value and the corresponding label, a possible default-value and it will return something of type a.

The basics

Simple validation

Monadic validation

A working example

Prepare your system

First install Formlets and Happstack-Server on your system:

$ cabal install formlets happstack-server

The example code

Put the following in a file called Main.hs:

moduleMainwhereimportControl.ApplicativeimportControl.Applicative.ErrorimportControl.Applicative.StateimportData.ListasListimportHappstack.ServerimportText.FormletsimportText.XHtml.Strict.FormletsimportText.XHtml.Strict((+++),(<<),(!))importqualifiedText.XHtml.StrictasXtypeMyForma=XHtmlFormIOadataDate=Date{month::Integer,day::Integer}derivingShowvalidDate::Date->BoolvalidDate(Datemd)=m`elem`[1..12]&&d`elem`[1..31]dateComponent::MyFormDatedateComponent=Date<$>inputInteger(Just1)<*>inputInteger(Just16)dateFull::MyFormDatedateFull=dateComponent`check`ensurevalidDate"This is not a valid date"handleDate::ServerPartTIOResponsehandleDate=withForm"date"dateFullshowErrorsInline(\d->okHtml$showd)dataUser=User{name::String,pass::String,birthdate::Date}derivingShowuserFull::MyFormUseruserFull=User<$>inputNothing<*>passwordNothing<*>dateFullhandleUser=withForm"user"userFullshowErrorsInline(\u->okHtml$showu)withForm::String->MyForma->(X.Html->[String]->ServerPartTIOResponse)->(a->ServerPartTIOResponse)->ServerPartTIOResponsewithFormnamefrmhandleErrorshandleOk=dirname$msum[methodSPGET$createForm[]frm>>=okHtml,withDataFnlookPairs$\d->methodSPPOST$handleOk'$simpled]wherehandleOk'd=dolet(extractor,html,_)=runFormStatedfrmv<-liftIOextractorcasevofFailurefaults->dof<-createFormdfrmhandleErrorsffaultsSuccesss->handleOkssimpled=List.map(\(k,v)->(k,Leftv))dshowErrorsInline::X.Html->[String]->ServerPartTIOResponseshowErrorsInlinerenderedFormerrors=okHtml$X.toHtml(showerrors)+++renderedFormcreateForm::Env->MyForma->ServerPartTIOX.HtmlcreateFormenvfrm=dolet(extractor,xml,endState)=runFormStateenvfrmreturn$X.form![X.method"POST"]<<(xml+++X.submit"submit""Submit")okHtml::(X.HTMLa)=>a->ServerPartTIOResponseokHtmlcontent=ok$toResponse$htmlPage$contenthtmlPage::(X.HTMLa)=>a->X.HtmlhtmlPagecontent=(X.header<<(X.thetitle<<"Testing forms"))+++(X.body<<content)main=simpleHTTP(nullConf{port=5000})(handleDate`mplus`handleUser)