Management of many is the same as management of few. It is a matter of organization.

—Sun Tzu

The valuable tool is not simply the one which allows us to move quickly from point ‘A’ to point ‘B’; it is the one that enables a rapid change in course to point ‘C’ when ‘B’ is no longer deemed desirable. Too often are we wooed by frameworks offering the elegant five line example which yields promises of simplicity and development speed only to find that, in many cases, the clarity of these examples are illusions which scale poorly with the demands of real-world requirements. As such, it is helpful to keep in mind that true simplicity is a matter of modularity and organization - not a matter of terseness.

In this post we present a minimal yet illustrative data binding example where we do not depend on any client side web framework. Instead we use a small set of easy to understand tools and techniques which together provide an alternative for modern client side development. These tools and techniques include Functional Reactive Programming (FRP) for data binding (provided by the Sodium package) and two embedded domain languages, BlazeHtml and Clay, which allow easy semantic decomposition and recombination of HTML and CSS respectively. We also make use of the GHCJS compiler which graciously compiles our Haskell to the JavaScript, HTML, and CSS needed to power the demonstration neighboring this paragraph.

main :: IO () () = runWebGUI $ \ webView -> do mainrunWebGUI\ webView <- getDocument webView docgetDocument webView <- findBody doc bodyfindBody doc htmlElementSetInnerHTML body html <- findInputElement doc "firstNameInput" firstNameInputFieldfindInputElement doc <- findInputElement doc "lastNameInput" lastNameInputFieldfindInputElement doc <- findInputElement doc "fullNameInput" fullNameInputFieldfindInputElement doc <- sync newEvent (firstNameEvents, publisherFn)sync newEvent <- sync newEvent (lastNameEvents, publisherFn')sync newEvent $ publishInputValues publisherFn elementOnkeyup firstNameInputFieldpublishInputValues publisherFn $ publishInputValues publisherFn' elementOnkeyup lastNameInputFieldpublishInputValues publisherFn' let fullNameEvents = firstNameEvents <> lastNameEvents fullNameEventsfirstNameEventslastNameEvents $ listen fullNameEvents $ const $ do synclisten fullNameEvents <- htmlInputElementGetValue firstNameInputField valhtmlInputElementGetValue firstNameInputField <- htmlInputElementGetValue lastNameInputField val'htmlInputElementGetValue lastNameInputField $ val <> " " <> val' htmlInputElementSetValue fullNameInputFieldvalval' return () ()

In the above code example we make use of GHCJS-DOM; a rather firmly typed JavaScript DOM manipulation library. As mentioned above, Sodium takes care of the reactivity of the little example by converting our DOM events into streams to be the subjects of manipulation. Programming using FRP allows us to handle our events and time-varying values in a functional way, that is, breaking problems down into small functions that are then composed into larger segments of functionality. In the above, we merge two streams and observe the result publishing data back to the user interface whenever we detect the occurrence of an event.

John Boyd went back to the Sabre vs MiG-15 ratios, because he was puzzled by the fact that on paper the MiG-15 was a better plane so why were the Sabres then so successful? Boyd learned that the bubble canopy of the Sabre gave the U.S. pilots better visibility and therefore better situational awareness. The full hydraulic flight controls allowed the Sabre pilots to transition from offensive to defensive maneuvers faster than his Soviet counterpart. Better observation and greater agility were the keys to the success of the Sabre pilots.

—J Lindberg, Fighter Tactics Academy

If this style of programming is new to you then take a moment to think about how we might modify the above code snippet to include a possible middle name input. Imagine a solution that you might use to drop all potential special characters from full names. If you find it easy to envision these solutions it is perhaps the product of our tools offering “situational awareness” rather than offering “rocket boosters” in hopes of quickly speeding us along to point ‘B’.

html :: String = renderHtml content htmlrenderHtml content content :: Html = do content nameForm css css :: Html = style $ toHtml C.css cssstyletoHtml C.css nameForm :: Html = section ! A.id "formContainer" $ nameFormsectionA.id $ do form "firstNameInput" "First Name" False labeledInputField "lastNameInput" "Last Name" False labeledInputField "fullNameInput" "Full Name" True labeledInputField labeledInputField :: AttributeValue -> Html -> Bool -> Html = p $ do labeledInputField id_ label_ readonly ! A.type_ "text" inputA.type_ ! A.maxlength "10" A.maxlength ! A.id id_ A.id id_ !? (readonly, A.readonly "" ) (readonly, A.readonly label label_

BlazeHTML was designed for optimal composability and, by virtue of being a functional programming library, the ease with which we can break down our HTML into semantic components is quite astonishing. Here we choose to decompose our little example into a simple nameForm comprised of labeledInputFields. Notice how our labeledInputField abstraction is not a cumbersome partial or sub-template its “just a function”. Even from this brief example we can see that, with very little ceremony, we are taking an already high-level domain language and crafting flexible components specifically tailored to the needs of our application.

css :: String = unpack . render $ formCss cssunpackrenderformCss formCss :: Css = do formCss formContainerCss formLabelCss inputCss advertisementCss formContainerCss :: Css = "#formContainer" ? formContainerCss do background niceGrey background niceGrey display inlineBlock 1 ) "#CDCDCD" border solid (px 5 ) (px 5 ) (px 5 ) (px 5 ) borderRadius (px) (px) (px) (px 0.75 ) (em 0.75 ) (em 0.75 ) (em 0.75 ) margin (em) (em) (em) (em 10 ) (px 10 ) (px 10 ) (px 10 ) padding (px) (px) (px) (px inputCss :: Css = "input" ? inputCss do padding (px 9 ) (px 9 ) (px 9 ) (px 9 ) padding (px) (px) (px) (px 1 ) "#E5E5E5" border solid (px 0 ) (rgba 0 0 0 255 ) outline solid (px) (rgba "Verdana" , "Tahoma" ] [sansSerif] fontFamily [] [sansSerif] 12 ) fontSize (px "#FFFFFF" :: Color ) background ( 0 ) (px 0 ) (px 8 ) (rgba 0 0 0 10 ) boxShadow (px) (px) (px) (rgba $ background linearGradient (straight sideTop) "#ffffff" , pct 0.1 ) [ (, pct "#eeeeee" , pct 14.9 ) , (, pct "#ffffff" , pct 85.0 ) , (, pct ] formLabelCss :: Css = "form label" ? formLabelCss do marginLeft (px 10 ) marginLeft (px 13 ) fontSize (px color coolBreezeGrey advertisementCss :: Css = "#ad" ? advertisementCss do marginLeft (px 5 ) marginLeft (px 10 ) fontSize (px fontStyle italic color coolBreezeGrey coolBreezeGrey :: Color = "#999999" coolBreezeGrey niceGrey :: Color = "#F5F5F5" niceGrey

Finally we take a look at Clay; the vehicle we use to realize our example’s dynamically generated CSS. In terms of semantic decomposition and easy recombination, Clay is as advantageous as BlazeHTML. The ease with which a user can compartmentalize her CSS using Clay should not be overlooked. The incremental and straight-forward creation of CSS pattern libraries is a direct benefit of the maneuverability that Clay gives us which stems from the principles we have been discussing throughout this post.

Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.

—John Carmack

Set against the backdrop of a tech-industry dominated by rigid frameworks, my hope is that this little post has sparked some interest in the style of client-side development provided by traditional functional programming. I encourage those of you who are not already familiar with these techniques to check out tools such as RxJS, ClojureScript, Bacon.js, PureScript, HTML Components, Elm, and others which are paving the way to a functional, modular client-side development experience.