shop products= do setHeader $ body . html setTimeouts 120 (30*24*60*60) loop emptyCart where loop cart= do r <- step . ask $ products <** showCart cart <+> wlink () << p << exit shop case r of case r of (Just bought,_) -> loop cart _ -> breturn cart
This flow has a single ask statement, but it is within a loop and step make this result persistent. Let's see what this means. setTimeouts establishes how long the process is running in memory and how long the serialized session data is recorded, respectively. Each product selected on each ask request is stored in the log. If, after two minutes, the user select another product, the process will be restarted and will recover the shopping cart state by re-executing the loop, taking as input the log content until the log is ﬁnished. Unless the user does not enter for a month, in which case, the log will be deleted and the shopping cart will appear empty. When the user press the "exit shop" link, the ﬂow will return the shop cart to the calling ﬂow. In the previous example, it is noteworthy that, if the user is adding products to the shopping cart, when he press the back button, the previous page will appear, with one product less in the cart. In this page, when he select other product and send the request, the application will backtrack one step in the loop, so the shopping cart will roll back from the last transaction and the shopping cart will reﬂect the page that the user is seeing. This synchronization of state and pages occurs on every case thanks to the backtracking mechanism. So that if the user is in a shopping cart and go back, he see pages with less and less items. When the user decides to proceed from this point the state of the shopping cart -because of the backtracking- will match "magically" with what is displayed in the user page.
All this is fine, but sometimes it is dangerous to go back. For example when a transaction that can not be undone has been done. For example a payment. For this purpose, it is necessary a cut in backtracking.
I added preventGoingBack, and pushed it to the Git repository. It perform this cut in backtracking when going back in the flow. When this condition is detected, ir executes a subflow that is the parameter. Usually this subflow consist of a single ask statement that present a single page with an error. When going forward, the statement is transparent:
ask $ wlink () << b << "press here to pay 100000 $ " payIt preventGoingBack . ask $ b << "You payed 10000 once" ++> wlink () << b << " Please press here" ask $ wlink () << b << "Press the back button to verify that you can not pay again" where payIt= liftIO $ print "paying"Here preventGoingBack does not permit to navigate back to code before himself. The user can go back in the browser, but what the user will see when he press a button or link in the flow, is the message of preventGoingBack and their available options, after which the flow will proceed normally.
This example uses the last version of MFlow at https://github.com/agocorona/MFlow.
It uses the last version of Workflow https://github.com/agocorona/Workflow
TCache : https://github.com/agocorona/TCache