A callback is an action that is executed when a widget receives valid input. MFlow has 'waction's They execute a sequence of pages when invoked. When the flow action is finished the display return to the original page, to execute further actions or to return the result of the page form. A waction takes complete control of the navigation.
This seaside widget counter is replicated five times in the same page:
There you can see the beautiful and short implementation and how nice Smalltalk is.
I tried hard, and finally I developed this functionality in MFlow. The same counter widget can be defined in a similarly short way in MFlow as such:
counterWidget n= do (h1 << show n ++> wlink "i" << b << " ++ " <|> wlink "d" << b << " -- ") `wcallback` \op -> case op of "i" -> counterWidget (n + 1) "d" -> counterWidget (n - 1)
Unlike the Seaside one, this is a recursive definition. The look and the behavior is the same than in the case of the Seaside counter (link above).
A page with a counter can be defined as such:
conterPage= ask $ counterWidget 0
And a page with five counters separated by horizontal lines can be defined as such:
multicounterPage= ask $ firstOf(replicate 5 counter) where counter= counterWidget 0 <++ hr
As in the case of the Seaside example, the five independent counters can be increased and decreased independently. Moreover in both cases they behave well with the back button.
wcallback replaces a widget with other widget when the first is validated (or does whatever possible in a View monad, including the return of a result). various chained callbacks can create a local flow. Actually, wcallback is implemented as a bind operation:
wcallback :: Monad m => View view m a -> (a -> View view m b) -> View view m b
The seaside callbacks works in a different way. they modify the same object, that is rendered in the next continuation.
The Haskell implementation and the use of RESTful URLs recently developed permits a pure RESTful version of the multicounter example, while in the Seaside case, the counters state in each page is stored in a continuation that is deleted after a timeout, so when you press again the same URL, the counters will appear zeroed. In the MFlow application, the GET requests created by the increment and decrement links produce URLs that are side effect free. If you bookmarck one of these URLs and send it by email, the receiver will see the same counter values when he press the link. That is very very nice for a stateful web framework.
I said that wcallback is a bind operations, but the real bind in the View monad is defined to perform a slightly different thing. While the former substitute the widget by other when the first is validated, bind add the new widget after the original one. This permits the creation of very interesting behaviours. For example, monadic forms instead of applicative ones that present input fields incrementally. Unlike in the case of Applicative forms, different dialogs can be presented depending on the user responses.
Here below is the code of a dynamic widget that interact with the server. It perform a monadic interaction with various steps and use a callback.
It is a functional logging widget. If the user is not anonymous, it simply display that the user is logged. If the user is not logged, it display a login box to enter the user name. once the name is entered, it display a password field below the user name. If the user is validated then the user is logged, the form dissapears and display the logged user name (in the callback). If the validation is erroneous, it displays a message below:
wlogin= do username <- getCurrentUser if username /= anonymous then return username else do name <- getString (Just $ "Enter username") <++ br pass <- getPassword <++ br val <- userValidate (name,pass) case val of Just msg -> notValid msg Nothing -> login name >> return name `wcallback` \name -> p << ("logged as " ++ name) ++> noWidgetYou can see how the widget interact nicely with the server.
You can see all of this in the following screencast. Excuse my dizziness. To my bad English -not used for quite a while- I add the tireness of many hours of programming. I recorded the video right after I solved the last great problem before making sure that all of this is possible in MFlow. Now there remain only bugs.
This version of MFlow is at the head branch:
The video will appear after Youtube end the processing: