| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 | <!doctype html><title>CodeMirror: Haskell-literate mode</title><meta charset="utf-8"/><link rel=stylesheet href="../../doc/docs.css"><link rel="stylesheet" href="../../lib/codemirror.css"><script src="../../lib/codemirror.js"></script><script src="haskell-literate.js"></script><script src="../haskell/haskell.js"></script><style>.CodeMirror {  border-top    : 1px solid #DDDDDD;  border-bottom : 1px solid #DDDDDD;}</style><div id=nav>  <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo                                                          src="../../doc/logo.png"></a>  <ul>    <li><a href="../../index.html">Home</a>    <li><a href="../../doc/manual.html">Manual</a>    <li><a href="https://github.com/codemirror/codemirror">Code</a>  </ul>  <ul>    <li><a href="../index.html">Language modes</a>    <li><a class=active href="#">Haskell-literate</a>  </ul></div><article>  <h2>Haskell literate mode</h2>  <form>    <textarea id="code" name="code">> {-# LANGUAGE OverloadedStrings #-}> {-# OPTIONS_GHC -fno-warn-unused-do-bind #-}> import Control.Applicative ((<$>), (<*>))> import Data.Maybe (isJust)> import Data.Text (Text)> import Text.Blaze ((!))> import qualified Data.Text as T> import qualified Happstack.Server as Happstack> import qualified Text.Blaze.Html5 as H> import qualified Text.Blaze.Html5.Attributes as A> import Text.Digestive> import Text.Digestive.Blaze.Html5> import Text.Digestive.Happstack> import Text.Digestive.UtilSimple forms and validation---------------------------Let's start by creating a very simple datatype to represent a user:> data User = User>     { userName :: Text>     , userMail :: Text>     } deriving (Show)And dive in immediately to create a `Form` for a user. The `Form v m a` typehas three parameters:- `v`: the type for messages and errors (usually a `String`-like type, `Text` in  this case);- `m`: the monad we are operating in, not specified here;- `a`: the return type of the `Form`, in this case, this is obviously `User`.> userForm :: Monad m => Form Text m UserWe create forms by using the `Applicative` interface. A few form types areprovided in the `Text.Digestive.Form` module, such as `text`, `string`,`bool`...In the `digestive-functors` library, the developer is required to label eachfield using the `.:` operator. This might look like a bit of a burden, but itallows you to do some really useful stuff, like separating the `Form` from theactual HTML layout.> userForm = User>     <$> "name" .: text Nothing>     <*> "mail" .: check "Not a valid email address" checkEmail (text Nothing)The `check` function enables you to validate the result of a form. For example,we can validate the email address with a really naive `checkEmail` function.> checkEmail :: Text -> Bool> checkEmail = isJust . T.find (== '@')More validation---------------For our example, we also want descriptions of Haskell libraries, and in order todo that, we need package versions...> type Version = [Int]We want to let the user input a version number such as `0.1.0.0`. This means weneed to validate if the input `Text` is of this form, and then we need to parseit to a `Version` type. Fortunately, we can do this in a single function:`validate` allows conversion between values, which can optionally fail.`readMaybe :: Read a => String -> Maybe a` is a utility function imported from`Text.Digestive.Util`.> validateVersion :: Text -> Result Text Version> validateVersion = maybe (Error "Cannot parse version") Success .>     mapM (readMaybe . T.unpack) . T.split (== '.')A quick test in GHCi:    ghci> validateVersion (T.pack "0.3.2.1")    Success [0,3,2,1]    ghci> validateVersion (T.pack "0.oops")    Error "Cannot parse version"It works! This means we can now easily add a `Package` type and a `Form` for it:> data Category = Web | Text | Math>     deriving (Bounded, Enum, Eq, Show)> data Package = Package Text Version Category>     deriving (Show)> packageForm :: Monad m => Form Text m Package> packageForm = Package>     <$> "name"     .: text Nothing>     <*> "version"  .: validate validateVersion (text (Just "0.0.0.1"))>     <*> "category" .: choice categories Nothing>   where>     categories = [(x, T.pack (show x)) | x <- [minBound .. maxBound]]Composing forms---------------A release has an author and a package. Let's use this to illustrate thecomposability of the digestive-functors library: we can reuse the forms we havewritten earlier on.> data Release = Release User Package>     deriving (Show)> releaseForm :: Monad m => Form Text m Release> releaseForm = Release>     <$> "author"  .: userForm>     <*> "package" .: packageFormViews-----As mentioned before, one of the advantages of using digestive-functors isseparation of forms and their actual HTML layout. In order to do this, we haveanother type, `View`.We can get a `View` from a `Form` by supplying input. A `View` contains moreinformation than a `Form`, it has:- the original form;- the input given by the user;- any errors that have occurred.It is this view that we convert to HTML. For this tutorial, we use the[blaze-html] library, and some helpers from the `digestive-functors-blaze`library.[blaze-html]: http://jaspervdj.be/blaze/Let's write a view for the `User` form. As you can see, we here refer to thedifferent fields in the `userForm`. The `errorList` will generate a list oferrors for the `"mail"` field.> userView :: View H.Html -> H.Html> userView view = do>     label     "name" view "Name: ">     inputText "name" view>     H.br>>     errorList "mail" view>     label     "mail" view "Email address: ">     inputText "mail" view>     H.brLike forms, views are also composable: let's illustrate that by adding a viewfor the `releaseForm`, in which we reuse `userView`. In order to do this, wetake only the parts relevant to the author from the view by using `subView`. Wecan then pass the resulting view to our own `userView`.We have no special view code for `Package`, so we can just add that to`releaseView` as well. `childErrorList` will generate a list of errors for eachchild of the specified form. In this case, this means a list of errors from`"package.name"` and `"package.version"`. Note how we use `foo.bar` to refer tonested forms.> releaseView :: View H.Html -> H.Html> releaseView view = do>     H.h2 "Author">     userView $ subView "author" view>>     H.h2 "Package">     childErrorList "package" view>>     label     "package.name" view "Name: ">     inputText "package.name" view>     H.br>>     label     "package.version" view "Version: ">     inputText "package.version" view>     H.br>>     label       "package.category" view "Category: ">     inputSelect "package.category" view>     H.brThe attentive reader might have wondered what the type parameter for `View` is:it is the `String`-like type used for e.g. error messages.But wait! We have    releaseForm :: Monad m => Form Text m Release    releaseView :: View H.Html -> H.Html... doesn't this mean that we need a `View Text` rather than a `View Html`?  Theanswer is yes -- but having `View Html` allows us to write these views moreeasily with the `digestive-functors-blaze` library. Fortunately, we will be ableto fix this using the `Functor` instance of `View`.    fmap :: Monad m => (v -> w) -> View v -> View wA backend---------To finish this tutorial, we need to be able to actually run this code. We needan HTTP server for that, and we use [Happstack] for this tutorial. The`digestive-functors-happstack` library gives about everything we need for this.[Happstack]: http://happstack.com/> site :: Happstack.ServerPart Happstack.Response> site = do>     Happstack.decodeBody $ Happstack.defaultBodyPolicy "/tmp" 4096 4096 4096>     r <- runForm "test" releaseForm>     case r of>         (view, Nothing) -> do>             let view' = fmap H.toHtml view>             Happstack.ok $ Happstack.toResponse $>                 template $>                     form view' "/" $ do>                         releaseView view'>                         H.br>                         inputSubmit "Submit">         (_, Just release) -> Happstack.ok $ Happstack.toResponse $>             template $ do>                 css>                 H.h1 "Release received">                 H.p $ H.toHtml $ show release>> main :: IO ()> main = Happstack.simpleHTTP Happstack.nullConf siteUtilities---------> template :: H.Html -> H.Html> template body = H.docTypeHtml $ do>     H.head $ do>         H.title "digestive-functors tutorial">         css>     H.body body> css :: H.Html> css = H.style ! A.type_ "text/css" $ do>     "label {width: 130px; float: left; clear: both}">     "ul.digestive-functors-error-list {">     "    color: red;">     "    list-style-type: none;">     "    padding-left: 0px;">     "}"    </textarea>  </form>  <p><strong>MIME types  defined:</strong> <code>text/x-literate-haskell</code>.</p>  <p>Parser configuration parameters recognized: <code>base</code> to  set the base mode (defaults to <code>"haskell"</code>).</p>  <script>    var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "haskell-literate"});  </script></article>
 |