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@gmail.com (@agocorona) <>< . issues & bugs .  Mflow Source code,     source code of this site




Runtime Templates

It is possible modify the layout of the active components at run-time. This means that no longer is necessary to define a layout before compilation for a formulary,  or for the arrangement of different widgets. 

Just create them without layout, and later the people in charge of the layout and styles will arrange the layout and the texts when the application is already tested. Then the layout never pollutes the code, and it may be decoupled also in time. 

The layout can be edited in a more powerful editor and inserted again and so on. As long as the designer do not modify the tags of forms and links created by the application, everithing goes fine. It can insert wathever content, formatting, apply styles etc.

The example list the names entered in a input form, but at the same time it permits the edition of the three pages: The first page, that has links. The form for entering a new name and the content of a list of results.It uses edTemplate 

NoteedTemplateList, has been deleted because it is not composable. The presentation of results uses witerate, a new primitive that permits to iterate the presentation of results with dField. This latter gets a widget and present it via ajax. witerate will present an widget, and successive calls to witerate will only change the content of the dFields. The rest of the widget will be unchanged. This permits even faster updates than autoRefresh. When edTemplate or template is used on top of witerate, the result is editable at runtime, and the span placeholders that are updated via ajax can be relocated within the layout of the template.  The presentation of results show all of this. This explain how the composable elements fit together.

See this page that explain in detail how it works.

Login as edituser/edituser to edit the template content using the login option. Logout to see the resulting layout. 

Running example

(in the light red box):

Menu

The title and this test has been added at runtime.
This link to the home has been also added.
You can edit this by logging as edituser/edituser 
(from time to time heroku will erase the changes)

enter a new name

List names

Exit to the home page



Source code:

-- | example of runtime templates, storage and query by using tcache
{-# OPTIONS -XDeriveDataTypeable -XNoMonomorphismRestriction  -XCPP #-}
module RuntimeTemplates where
import Data.TCache.DefaultPersistence
import Data.TCache.IndexQuery
import Control.Workflow.Configuration
import Data.Typeable
import Data.Monoid
import Data.ByteString.Lazy.Char8 as BS hiding (index)
import Control.Exception(SomeException)
import Control.Monad (when)

-- #define ALONE -- to execute it alone, uncomment this

-- Note:  there are static links in templates in the main menu. unless they
-- are updated using  witerate/dField, the main menu of this example will not
-- work when the app run alone since the expected paths are different.
#ifdef ALONE
import MFlow.Wai.Blaze.Html.All  hiding(name,select,base)
import Debug.Trace
(!>) = flip trace
main= runNavigation "" $ transientNav runtimeTemplates
#else
import MFlow.Wai.Blaze.Html.All hiding(name,select,base, page)
import Menu
#endif



data  MyData= MyData{name :: String} deriving (Typeable,Read, Show)  

instance Indexable MyData where  key=  name     -- just to notify what is the key of the register

instance Serializable MyData where    -- default persistence
   serialize=  pack . show
   deserialize=   read . unpack
   setPersist =   \_ -> Just filePersist      --  in files

data Options= NewName | ListNames  | Exit deriving (Show, Typeable,Eq)

newtype NextReg = NextReg Int deriving Typeable

runtimeTemplates= do
     -- runConfiguration is usedd to create some example registers
     liftIO $ runConfiguration "createDataforTemplates" $ do
             ever $ index name  -- index the name field, so I can query  for it later
                                -- better, should put this sentence in main

             -- create ['a'..'z'] entries. Only the first time
             -- runConfiguration prevent the execution of this again
             once $ atomically $ mapM_ (newDBRef . MyData . return) ['a'..'z']
     process

process= do
     setSessionData $ NextReg 0  
     r <- page  $  edTemplate "edituser" "menuallnames" 
                $ wlink NewName   << p << "enter a new name"
              <|> wlink ListNames << p << "List names"
              <|> wlink Exit      << p << "Exit to the home page"

     case r of
         Exit -> return()
         NewName -> do
              page  $ edTemplate "edituser" "enterallnames"
                    $ pageFlow "enter"
                    $ witerate (
                            do  noCache
                                name <- dField (getString Nothing `validate` jsval)
                                        <** submitButton "ok" <++ br
                                -- store the register in the cache
                                -- Later will be written by the default persistence automatically
                                liftIO . atomically . newDBRef $ MyData name   
                        **> do
                             n <- countRegisters
                             dField (wraw $ b << show n) <++ fromStr " registers added ")
                   **> wlink () << b << "exit"

         ListNames -> do
              -- query for all the names stored in all the registers
              allnames <- getAllNames

              let len= Prelude.length allnames

              page $ pageFlow "iter" $ iterateResults allnames len
                 **> wlink "templ" << p << "click here to show the result within a runtime template"

              setSessionData $ NextReg 0
              page $ edTemplate "edituser" "listallnames"
                   $ pageFlow "iter" $ iterateResults allnames len
                 **> wlink "scroll" << p << "click here to present the results as a on-demand-filled scroll list"

              setSessionData $ NextReg 0
              page $ pageFlow "upd" $ appendUpdate ( do
                          NextReg ind <- getSessionData `onNothing` return (NextReg 0)
                          getData ind     len allnames <++ br
                          getData (ind+1) len allnames <++ br
                          getData (ind+2) len allnames <++ br
                          getData (ind+3) len allnames <++ br
                          setSessionData . NextReg $ next ind len
                          wlink "more" << b << "more" <++ br)

                  **> wlink () << p << "click here to go to the menu"

     if r == Exit then return() else process

     where
     getAllNames= liftIO . atomically $ select name $ name .>.  ""

     countRegisters= (getAllNames >>= return . Prelude.length)


     iterateResults allnames len = witerate $ do
          maxAge 300
          NextReg ind <- getSessionData `onNothing` return (NextReg 0)
          dField(getData  ind    len allnames) <++ br
          dField(getData (ind+1) len allnames) <++ br
          dField(getData (ind+2) len allnames) <++ br
          dField(getData (ind+3) len allnames) <++ br
          r <-    dField (wlink (next ind len) << b << "next" ) <++ fromStr " "
              <|> dField (wlink (prev ind len) << b << "prev")
              <|> restp
          setSessionData $ NextReg r


     getData i len all=  wraw . fromStr $ if i >= len || i < 0 then "" else all !! i
     next i len = case i > len  of  True -> 0 ; _ -> i + 4
     prev i len = case i < 0 of True -> len; _ -> i - 4

     jsval s=
         if Prelude.length s > 10 then do
           return $ Just  $ b << "length must be less than 10 chars"
         else return Nothing

comments powered by Disqus