However sometimes, the back button is used for navigation and we want to keep the state, not to roll it back. In the previous example of the shop, the cart is passed as a parameter. That may be tedious if the flow is complicated. To solve these two problems, I created getSessionData and setSessionData. which stores and retrieves user-defined data in the session, by means of a map indexed by data type. In this way the user don´t need to pass its application data by parameters, neither it need to embed its own state monad. But also, the programmer when pressing back, can choose to backtrack to previous state values or not, depending on the nature of the flow.
This session data works in the Flow monad as well as the View monad. That means that it can be used in both sides of ask. Since when backtracking the only code executed is the View monadic code behind ask, getSessionData, when it is in View monad, will retrieve the last state no matter if it is on backtracking. This is the way to keep the user-defined state actualized to the last value when backtracking.
In the example below, the shopping cart is stored as session data. In the ask statement that show the shopping cart, getSessionData is used in the View monad (in bold), so when backtracking the code will get the last shopping cart, so no roll-back effect will appear and the back button can be used for navigation purposes. Here is a loop with an alternance between buying from the catalog and showing the shopping cart. The loop, and the navigation can be executed forward, by pressing the links, and backward, by means of the back button. The result is the same.
If getSessionData is not in the View monad, and the cart variable in scope is used, then this variable will keep its value that it had at each moment of the loop, so when going back we will see fewer and fewer items in the shopping cart.
Here step is used, so the state is persistent. setTimeouts will kill the process after two minutes. After one month, if the user has not returned, the state will be erased and the shopping cart will be empty. This is the complete program:
module Main where import MFlow.Wai.Blaze.Html.All import Data.Typeable import Data.String(fromString) import qualified Data.Vector as V main= do addMessageFlows [("", runFlow $ shop ["iphone","ipad","ipod"])] wait $ run 8081 waiMessageFlow shop products= do setHeader $ html . body setTimeouts 120 (30*24*60*60) catalog where catalog = do bought <- step . ask $ showProducts products cart <- getSessionData `onNothing` return emptyCart let n = cart V.! bought setSessionData $ cart V.// [(bought,n+1)] step $ do r <- ask $ do cart <- span=""> getSessionData `onNothing` return emptyCart p << showCart cart ++> wlink True << b << "continue shopping" <|> wlink False << p << "proceed to buy" if( r== False) then ask $ wlink () << "not implemented, click here" else return () catalog emptyCart= V.fromList $ take (length products) (repeat (0::Int)) showProducts xs= firstOf $ map (\(i,x) -> wlink i (p << x)) $ zip [0..] xs ->
MFlow : http://github.com/agocorona/MFlow