openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

index.html (9381B)


      1 <!doctype html>
      2 
      3 <title>CodeMirror: Haskell-literate mode</title>
      4 <meta charset="utf-8"/>
      5 <link rel=stylesheet href="../../doc/docs.css">
      6 
      7 <link rel="stylesheet" href="../../lib/codemirror.css">
      8 <script src="../../lib/codemirror.js"></script>
      9 <script src="haskell-literate.js"></script>
     10 <script src="../haskell/haskell.js"></script>
     11 <style>.CodeMirror {
     12   border-top    : 1px solid #DDDDDD;
     13   border-bottom : 1px solid #DDDDDD;
     14 }</style>
     15 <div id=nav>
     16   <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo
     17                                                           src="../../doc/logo.png"></a>
     18 
     19   <ul>
     20     <li><a href="../../index.html">Home</a>
     21     <li><a href="../../doc/manual.html">Manual</a>
     22     <li><a href="https://github.com/codemirror/codemirror">Code</a>
     23   </ul>
     24   <ul>
     25     <li><a href="../index.html">Language modes</a>
     26     <li><a class=active href="#">Haskell-literate</a>
     27   </ul>
     28 </div>
     29 
     30 <article>
     31   <h2>Haskell literate mode</h2>
     32   <form>
     33     <textarea id="code" name="code">
     34 > {-# LANGUAGE OverloadedStrings #-}
     35 > {-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
     36 > import Control.Applicative ((<$>), (<*>))
     37 > import Data.Maybe (isJust)
     38 
     39 > import Data.Text (Text)
     40 > import Text.Blaze ((!))
     41 > import qualified Data.Text as T
     42 > import qualified Happstack.Server as Happstack
     43 > import qualified Text.Blaze.Html5 as H
     44 > import qualified Text.Blaze.Html5.Attributes as A
     45 
     46 > import Text.Digestive
     47 > import Text.Digestive.Blaze.Html5
     48 > import Text.Digestive.Happstack
     49 > import Text.Digestive.Util
     50 
     51 Simple forms and validation
     52 ---------------------------
     53 
     54 Let's start by creating a very simple datatype to represent a user:
     55 
     56 > data User = User
     57 >     { userName :: Text
     58 >     , userMail :: Text
     59 >     } deriving (Show)
     60 
     61 And dive in immediately to create a `Form` for a user. The `Form v m a` type
     62 has three parameters:
     63 
     64 - `v`: the type for messages and errors (usually a `String`-like type, `Text` in
     65   this case);
     66 - `m`: the monad we are operating in, not specified here;
     67 - `a`: the return type of the `Form`, in this case, this is obviously `User`.
     68 
     69 > userForm :: Monad m => Form Text m User
     70 
     71 We create forms by using the `Applicative` interface. A few form types are
     72 provided in the `Text.Digestive.Form` module, such as `text`, `string`,
     73 `bool`...
     74 
     75 In the `digestive-functors` library, the developer is required to label each
     76 field using the `.:` operator. This might look like a bit of a burden, but it
     77 allows you to do some really useful stuff, like separating the `Form` from the
     78 actual HTML layout.
     79 
     80 > userForm = User
     81 >     <$> "name" .: text Nothing
     82 >     <*> "mail" .: check "Not a valid email address" checkEmail (text Nothing)
     83 
     84 The `check` function enables you to validate the result of a form. For example,
     85 we can validate the email address with a really naive `checkEmail` function.
     86 
     87 > checkEmail :: Text -> Bool
     88 > checkEmail = isJust . T.find (== '@')
     89 
     90 More validation
     91 ---------------
     92 
     93 For our example, we also want descriptions of Haskell libraries, and in order to
     94 do that, we need package versions...
     95 
     96 > type Version = [Int]
     97 
     98 We want to let the user input a version number such as `0.1.0.0`. This means we
     99 need to validate if the input `Text` is of this form, and then we need to parse
    100 it to a `Version` type. Fortunately, we can do this in a single function:
    101 `validate` allows conversion between values, which can optionally fail.
    102 
    103 `readMaybe :: Read a => String -> Maybe a` is a utility function imported from
    104 `Text.Digestive.Util`.
    105 
    106 > validateVersion :: Text -> Result Text Version
    107 > validateVersion = maybe (Error "Cannot parse version") Success .
    108 >     mapM (readMaybe . T.unpack) . T.split (== '.')
    109 
    110 A quick test in GHCi:
    111 
    112     ghci> validateVersion (T.pack "0.3.2.1")
    113     Success [0,3,2,1]
    114     ghci> validateVersion (T.pack "0.oops")
    115     Error "Cannot parse version"
    116 
    117 It works! This means we can now easily add a `Package` type and a `Form` for it:
    118 
    119 > data Category = Web | Text | Math
    120 >     deriving (Bounded, Enum, Eq, Show)
    121 
    122 > data Package = Package Text Version Category
    123 >     deriving (Show)
    124 
    125 > packageForm :: Monad m => Form Text m Package
    126 > packageForm = Package
    127 >     <$> "name"     .: text Nothing
    128 >     <*> "version"  .: validate validateVersion (text (Just "0.0.0.1"))
    129 >     <*> "category" .: choice categories Nothing
    130 >   where
    131 >     categories = [(x, T.pack (show x)) | x <- [minBound .. maxBound]]
    132 
    133 Composing forms
    134 ---------------
    135 
    136 A release has an author and a package. Let's use this to illustrate the
    137 composability of the digestive-functors library: we can reuse the forms we have
    138 written earlier on.
    139 
    140 > data Release = Release User Package
    141 >     deriving (Show)
    142 
    143 > releaseForm :: Monad m => Form Text m Release
    144 > releaseForm = Release
    145 >     <$> "author"  .: userForm
    146 >     <*> "package" .: packageForm
    147 
    148 Views
    149 -----
    150 
    151 As mentioned before, one of the advantages of using digestive-functors is
    152 separation of forms and their actual HTML layout. In order to do this, we have
    153 another type, `View`.
    154 
    155 We can get a `View` from a `Form` by supplying input. A `View` contains more
    156 information than a `Form`, it has:
    157 
    158 - the original form;
    159 - the input given by the user;
    160 - any errors that have occurred.
    161 
    162 It is this view that we convert to HTML. For this tutorial, we use the
    163 [blaze-html] library, and some helpers from the `digestive-functors-blaze`
    164 library.
    165 
    166 [blaze-html]: http://jaspervdj.be/blaze/
    167 
    168 Let's write a view for the `User` form. As you can see, we here refer to the
    169 different fields in the `userForm`. The `errorList` will generate a list of
    170 errors for the `"mail"` field.
    171 
    172 > userView :: View H.Html -> H.Html
    173 > userView view = do
    174 >     label     "name" view "Name: "
    175 >     inputText "name" view
    176 >     H.br
    177 >
    178 >     errorList "mail" view
    179 >     label     "mail" view "Email address: "
    180 >     inputText "mail" view
    181 >     H.br
    182 
    183 Like forms, views are also composable: let's illustrate that by adding a view
    184 for the `releaseForm`, in which we reuse `userView`. In order to do this, we
    185 take only the parts relevant to the author from the view by using `subView`. We
    186 can then pass the resulting view to our own `userView`.
    187 We have no special view code for `Package`, so we can just add that to
    188 `releaseView` as well. `childErrorList` will generate a list of errors for each
    189 child of the specified form. In this case, this means a list of errors from
    190 `"package.name"` and `"package.version"`. Note how we use `foo.bar` to refer to
    191 nested forms.
    192 
    193 > releaseView :: View H.Html -> H.Html
    194 > releaseView view = do
    195 >     H.h2 "Author"
    196 >     userView $ subView "author" view
    197 >
    198 >     H.h2 "Package"
    199 >     childErrorList "package" view
    200 >
    201 >     label     "package.name" view "Name: "
    202 >     inputText "package.name" view
    203 >     H.br
    204 >
    205 >     label     "package.version" view "Version: "
    206 >     inputText "package.version" view
    207 >     H.br
    208 >
    209 >     label       "package.category" view "Category: "
    210 >     inputSelect "package.category" view
    211 >     H.br
    212 
    213 The attentive reader might have wondered what the type parameter for `View` is:
    214 it is the `String`-like type used for e.g. error messages.
    215 But wait! We have
    216     releaseForm :: Monad m => Form Text m Release
    217     releaseView :: View H.Html -> H.Html
    218 ... doesn't this mean that we need a `View Text` rather than a `View Html`?  The
    219 answer is yes -- but having `View Html` allows us to write these views more
    220 easily with the `digestive-functors-blaze` library. Fortunately, we will be able
    221 to fix this using the `Functor` instance of `View`.
    222     fmap :: Monad m => (v -> w) -> View v -> View w
    223 A backend
    224 ---------
    225 To finish this tutorial, we need to be able to actually run this code. We need
    226 an HTTP server for that, and we use [Happstack] for this tutorial. The
    227 `digestive-functors-happstack` library gives about everything we need for this.
    228 [Happstack]: http://happstack.com/
    229 
    230 > site :: Happstack.ServerPart Happstack.Response
    231 > site = do
    232 >     Happstack.decodeBody $ Happstack.defaultBodyPolicy "/tmp" 4096 4096 4096
    233 >     r <- runForm "test" releaseForm
    234 >     case r of
    235 >         (view, Nothing) -> do
    236 >             let view' = fmap H.toHtml view
    237 >             Happstack.ok $ Happstack.toResponse $
    238 >                 template $
    239 >                     form view' "/" $ do
    240 >                         releaseView view'
    241 >                         H.br
    242 >                         inputSubmit "Submit"
    243 >         (_, Just release) -> Happstack.ok $ Happstack.toResponse $
    244 >             template $ do
    245 >                 css
    246 >                 H.h1 "Release received"
    247 >                 H.p $ H.toHtml $ show release
    248 >
    249 > main :: IO ()
    250 > main = Happstack.simpleHTTP Happstack.nullConf site
    251 
    252 Utilities
    253 ---------
    254 
    255 > template :: H.Html -> H.Html
    256 > template body = H.docTypeHtml $ do
    257 >     H.head $ do
    258 >         H.title "digestive-functors tutorial"
    259 >         css
    260 >     H.body body
    261 > css :: H.Html
    262 > css = H.style ! A.type_ "text/css" $ do
    263 >     "label {width: 130px; float: left; clear: both}"
    264 >     "ul.digestive-functors-error-list {"
    265 >     "    color: red;"
    266 >     "    list-style-type: none;"
    267 >     "    padding-left: 0px;"
    268 >     "}"
    269     </textarea>
    270   </form>
    271 
    272   <p><strong>MIME types
    273   defined:</strong> <code>text/x-literate-haskell</code>.</p>
    274 
    275   <p>Parser configuration parameters recognized: <code>base</code> to
    276   set the base mode (defaults to <code>"haskell"</code>).</p>
    277 
    278   <script>
    279     var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "haskell-literate"});
    280   </script>
    281 
    282 </article>