Monday, June 10, 2013

Callbacks: Dynamic Web widgets and dynamic forms without JavaScript

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.

But it would be nice to execute a flow in each widget in the page simultaneously. Each widget in the page can have dynamic behaviors without using JavaScript. Seaside is an incredible web framework developed in a very nice language: Smalltalk. It has such functionality.

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) ++> noWidget

You 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:
 

No comments: