MFlow  Create and maintain dynamic Web applications as easy and fast as console applications
Thou shall not write request handlers
 This release: 11/06/2014. (@agocorona) <>< . issues & bugs .  Mflow Source code,     source code of this site

Example for online creation and edition of formularies 

This example is a basic reference application for GUI-like applications. An application for the creation and edition of forms. in MFlow. This is the first version of the application. The successive version will show new functonalities. This is the basic version. The second version add versioning and undo. The third version avoid page refreshes.

Here is the application running 

And this is the complete current source code

This is a screenshoot

How it works

MFlow is a web framework intended for high level web programming. It is possible to create multipage as well as complex single page applications with dinamic behaviours in pure haskell, without the need of Javascript programming

This application has a dynamic menu to add widgets to an WYSIWYG editor, so you can create a page fragment with text and form elements. You can arange the layout with the editor. Finally, yo can test the form created.

The example is intended to be a reference implementation for complex single page applications. It does not have some sophistications like multilevel undo, persistence, widget autorefresh , field validation messages etc that will be addded to other examples coming.

This text is intentionally terse. It does not matter if you don't understand it. The idea is that you must play with the code. Modify it. Later you may understand more and more the text.

To run the application:

>runghc Demos/GenerateForm.hs 

The top level is this function that contain the navigation of the tree pages mentioned: creation of the form, form input and show results of the form:

genForm= do
    let title= "form.html"
    initFormTemplate title

    desc <- page $ createForm title

    r <- page $ b "This is the form created, asking for input"
           ++> hr
           ++> generateForm title desc
           <++ br
           <** pageFlow "button" (submitButton "submit")

    page $ h3 "results of the form:" ++> p << show r ++> noWidget

The next thing is the definition of the form data structure that will be generated by the application. As you see, a form can contain other form fragments, but this will not be used in this application. The definition of the kinds of widgets that can be added can be made extensible too, but this is not the goal of this demo.

type Template= String
data WType = Intv | Stringv | TextArea |OptionBox[String]
           | WCheckBoxes [String] | Form Template [WType] deriving (Typeable,Read,Show)

Then it comes the initialization. The generated form is defined by the structure above and the template. The latter is a html file that contains the layout edited by the user. Here below both components are initialized.  The layout will be contained in an editable text field element. The former is stored in the a session variable. There is a sequence variable Seq that is used to assign sequential identifiers to the form fields. That variable is also initialized.

initFormTemplate title= do
  liftIO $ writetField title $
      p "( delete this line. Press the save button to save the edits)"

  setSessionData ([] :: [WType])
  setSessionData $ Seq 0

A form elements return data of type Result. So a form with many elements will return a result of type  [Result]

data Result = forall a.(Typeable a, Show a) => Result a deriving (Typeable)

instance Show Result where
  show (Result x)= show x

Each WType data  has a genElem that generates the corresponding form element. That is a formlet/widget that generates the Result. For the  OptionBox and the WCheckBoxes, press the links to see how to construct them.

genElem Intv= Result <$> getInt Nothing
genElem Stringv= Result <$> getString Nothing
genElem TextArea= Result <$> getMultilineText ""
genElem (OptionBox xs) =
    Result <$> getSelect (setSelectedOption ""(p "select a option") <|>
               firstOf[setOption op (fromStr op) | op <- xs])

genElem (WCheckBoxes xs) =
    Result <$> getCheckBoxes(mconcat[setCheckBox False x <++ (fromStr x) | x <- xs])

genElem (Form temp desc)= Result <$> generateForm temp desc

The last definition for a Form with a template and a description is recursive. To generate the complete form with the list of WType elements and the template:

generateForm :: View Html IO Result
generateForm title xs=
   input ! At.type_ "hidden" ! name "p0" ! value "()"
   ++> template title
   (pageFlow "" $ allOf $ map genElem xs )

The above code is a Widget that uses blaze-html, in the IO monad, that is, it has signature: View Html IO Result. I do not use type signatures too much in order to avoid intimidating non haskellers.

The definition uses template, that gets a html template identified by a key, title, and a widget that process the input, defined by  all of the inputs of the elements of the form. 

pageFlow is used to start the form field identifiers from zero on.

The hidden input field is there to match what 
allOf expect to find. The template will contain the form inputs that the genElem's expect, with some addition of text and layout added by the editor with edTemplate (below)

If we eliminate template title, what would appear is the form devoid of the editions, but the behaviour would be the same.

And now the hard part. A complex page in MFlow is typically made of applicative combinators, each one contains a monadic expression. The operator  <** is like the applicative <* except that the former assures that both sides are executed, while the latter does not execute the second term if the first validates. 

createForm title=
 divmenu <<< ( wlogin
  **> do br ++> wlink ("save" :: String) << b "Save the form and continue"
            <++ br <> "(when finished)"
         getSessionData `onNothing` return []
  <** do
       wdesc <- chooseWidget <++ hr
       desc <- getSessionData `onNothing` return []
       setSessionData $ mappend desc [wdesc]
       content <- liftIO $ readtField mempty title
       fieldview <- generateView wdesc
       liftIO . writetField title $ content <> br <> fieldview
  <** divbody <<< wform (edTemplate "edituser" title (return ()) )

divbody= div ! "float:right;width:65%"
divmenu= div ! "background-color:#EEEEEE;float:left;margin-left:10px;margin-right:10px;overflow:auto;"

the operator <<< encloses a widget within a tag. There are two div tags, one for the menu, and other for the rest of the page.

Here are four elements. The direction of the **> <** operators means that only the second will return a valid output, since the other's output is ignored. the elements are:

  • The wlogin widget 
  • The save link. Since it is a monadic expression, unless the link is pressed, it will not return a valid output. When pressed, it read the session data generated by the other two widgets and will permit the continuation of the page flow.
  • The menu of  different form elements to choose. When one is clicked which pick an element, add it to the list in the session and add the rendering of the widget to the form. All of this is on the menu on the left
  • The form editing in the main part of the window. It uses  edTemplate element that can edit the "edituser" user.  The  menu widget above also add form elements when the options are pressed

Here is how the rendering of the widget is obtained when s form element id clicked in the menu and added to the edition . It is critical to name the identifiers of the formlets sequentially from 1 on. the next widget added must be named sequentially. For that matter the sequence is stored and retrieved from the session data. 

Then the element is generated (genElem ), executed (runView) and the rendering is returned:

newtype Seq= Seq Int deriving (Typeable)

generateView desc= View $ do
    Seq n <- getSessionData `onNothing` return (Seq 0)
    s <- get
    let n'= if n== 0 then 1 else n
    put s{mfSequence= n'}
    FormElm render _ <- runView $ genElem desc
    n'' <- gets mfSequence
    setSessionData $ Seq n''
    return $ FormElm [] $ Just ( br <> br <> mconcat render :: Html)

Here is the menu, that contains the different widgets that can be added to the edition.  The cascade menu works as the main menu of the mflowdemo site (see this article)  

       (p $ a ! At.href "/" $ "home") ++>

       (p <<< do absLink ("text":: String) "text field"
                 ul <<<(li <<< wlink Intv "returning Int"
                    <|> li <<< wlink Stringv "returning string"))

       <|> p <<< do absLink TextArea "text area"

       <|> p <<< do
              absLink ("check" :: String) "checkBoxes"
              ul <<< getOptions "comb"

       <|> p <<< do
              absLink ("options" :: String) "options"
              ul <<< getOptions "opt"

But the two links on the bottom call getOptions when pressed, and this is a complex widget. As you can see below, it is made of two parts. One has two buttons: "create",  get the session data containing the options chosen. "clear" reset the options.

The second part is a input box and a button that add new options to the session data. Since it is used for both widgets, it is necessary to detect what of both is processing at each moment. If the user change from one to the other option, the set of options is cleared and restarted again.

stop= noWidget

getOptions pf =
      r <- wform $ submitButton "create" <|> submitButton "clear"

      case r of
        "create" -> do
          ops <- getSessionData
          case ops of
            Nothing -> stop
            Just elem -> return elem
        "clear" -> do
           delSessionData (undefined :: WType)

    <** do
        op <- wform $ getString Nothing <! [("size","8")
                       <** submitButton "add" <++ br

        mops <- getSessionData
        ops' <- case (mops,pf) of
           (Nothing, "comb") -> do setSessionData $ WCheckBoxes [op] ; return [op]
           (Nothing, "opt") -> do setSessionData $ OptionBox [op] ; return [op]
           (Just (OptionBox _), "comb") -> do setSessionData $ WCheckBoxes [op] ; return [op]
           (Just (WCheckBoxes _),"opt") -> do setSessionData $ OptionBox [op] ; return [op]
           (Just (WCheckBoxes ops),"comb") -> do
               let ops'= nub $ op:ops
               setSessionData . WCheckBoxes $ ops'
               return ops'
           (Just (OptionBox ops),"opt") -> do
               let ops'= nub $ op:ops
               setSessionData . OptionBox $ ops'
               return ops'
        wraw $ mconcat [p << op | op <- ops']

Finally the last line at the bottom display the options entered so far.

stop is defined as an invalid widget (noWidget) that stops the monadic sequence, and, like in the former case, the only element that can return a valid output is the "create" button, that return the result to the menu and trigger the addition of the widget to the edTemplate. widget.

And voila, a form editor. The next page uses generateForm  to re-generate the form from the description and the formatting to get input. As I said above, the form has no validation messages, since the layout mask the validation messages. To have  "holes" in the template, we need witerate and dField modifiers. But that will be done in the next example. Stay tuned

comments powered by Disqus