I have been trying to port the formlets concept to the client side with a lot of success. I think. The applicative and monadic code of the MFlow widgets works almost unchanged when compiled to Javacript with the haste compiler.
I called it hplayground since it is more marketable than Haskell-js-reactive-widgets . And it seems to me something that convert the browser into a playground to essay different things. The DOM tree structure, the HTML formatting, the events, are not an impediment for making simple things simply. It helps making complicated things easy since all of them work in harmony.
The examples are alive here:
http://mflowdemo.herokuapp.com/noscript/wiki/browserwidgets
What i achieved? the possibility to create applications in the browser as fast as easy as console applications and have console, reactive, window-oriented and spreadsheet-like behaviours for free. The mix is a weird but powerful programming something. A kind of having your cake and eat it too.
The problem with the current haskell/elm reactive-declarative developments in the client side is as follows: They all create static layouts with holes that will be modified declaratively expressing reactive dependencies. The result is that page content can not be modified, except the holes.
I want everything, including the layout, to be modifiable by the events. and this means monadic code. And this means that the layout and the events must run across ifs, elses, case and branches of the code.
The hplayground idea is to use applicative code for static layouts but also monadic code for dynamic updates The events propagate downstream within the monadic code. Whenever that a widget raises an exception, the rest of the monadic computation is triggered, generating new rendering. The events, the computation, the HTML.DOM modifications of the layout follows the same natural and intuitive path within the monadic computation.
This is an static layout with to input boxes with a dynamic part at the end that show the results when the two input boxes validates:
sumtwonumbers = p "sum two numbers and append the result" ++>(p <<< dor <- (+) <$> inputInt Nothing `raiseEvent` OnKeyUp <++ br
<*> inputInt Nothing `raiseEvent` OnKeyUp <++ br
p <<< fromStr "result: " ++> b (show r) ++> return())
This is a combination of applicative (static) and monadic (dynamic) layout with reactive event handling. When one of the two fields receive a character, the result is updated. If some of the input boxes do not validate, the result line is deleted. It appears when the two boxes validate again.
inputInt :: Maybe Int -> Widget Int
How that happens? raiseEvent attach an event handler to the input box. This event handler i re-executes the the current widget and the rest of the monadic computation down. This rest of the computation rewrite the HTML that generated previously, in this case, the result. So result is erased and rewritten when one of the two input boxes are modified.
the layout: 'br' 'p' and the input box etc are elements of the haste-perch library, described in the previous post. They are added to the active input elements by means of operators for enclosing (<<<) prepending (++>) and postpending (<++).
<$> and <*> are ordinary applicative combinators and (+) is the sum.
So events, layout and computation follow the same path. You can see a more complex example:
recursivesum :: Widget ()recursivesum = p "sum recursively n numbers. When enters 0, present the result" ++> sumr 0wheresumr r=dor' <- inputInt Nothing `raiseEvent` OnKeyUpif r'== 0then br ++> fromStr "result: " ++> b (show r) ++> emptyelse dob (show $ r+r') ++> br ++> return ()sumr (r+r')
Here there is a input box. Initially it is empty so it does not validate and the computation stop there. If the user enter 0, it present the result. If not it call itself recursively, so it creates another input box and so on. Until 0 is entered and the current sum is presented again below the last input box. If you change an intermediate value in a input box above you can see that the following input boxes are deleted, since the event handler deletes the layout of the continuation and rewrite it.
There is a version in the examples that remember the old entries using a session context.
This example has a fixed number of input boxes:
sumfold n = p ("This widget sum "++ show n ++" numbers and append the result using a fold") ++>(p <<< dor <- foldl (<>) (return 0) . take n $ repeat $ inputInt Nothing `raiseEvent` OnKeyUp <++ br
br ++> fromStr "result: " ++> b (show r) ++> return ())
Since Widget a is a instance of monoid as long as a is a Monoid, any number of them can be folded.
Graphics are possible also, no news here, since the canvas internal layout is managed by his own monad in Haste. And that is right. However if you look at the "function draw" example and the "gallery" example you can see that the canvas is erased and recreated when the input boxes or a timeout event arrives. The reactive effect of the function draw example is noteworthy.
One more example: The pascal triangle:
-- pascal triangle http://www.haskell.org/haskellwiki/Blow_your_mindpascal = iterate (\row -> zipWith (+) ([0] ++ row) (row ++ [0])) [1] :: [[Int]]showpascal n= p << ("Show " ++ show n ++ " rows of the Pascal triangle ")++> mconcat[p ! atr "style" "text-align:center" $ row | row <- take n pascal]
++> empty -- the applicative empty === noWidget
Things are not yet finished. I have to create an MFlow application for people to play with the idea. I have to upload the last version of the examples to the page
Where these examples can be seen in action
I have to manage mouse events. It probably will use the same semantics than the wtimeout call, used in the gallery example.
Finally, for the updates of non local elements of the layout that not follow the default downstream flow of a monadic computation (think for example on something like a spreadsheet) Some of them can be done using Haste.DOM primitives , but I have to create high level abstractions like the cell concept. But I have not finished it yet.
By the way, Haste works like a charm. It produces short efficient code. All the examples fit in 100k of javascript code.
At this moment only text boxes and buttons work. I have to add the dropdowns option buttons and wlinks. so that full window like applications can be created. I just follow the path of higher resistance and leave the easy things for later.
An finally, some day I will take time to make my presentations, my examples and my texts more readable and more pretty and at least not as dyslexic as they are, but God did not called me to go this path. Too busy programming. Blogger conspirates against me, but I have no clue about what happens with the layout of this page.
To summarize, with little more effort that what you would employ in the creation of a console application and with the same structure, you can create a live dynamic application running in any browser.
No comments:
Post a Comment