The Modular, Functional Client Side
Management of many is the same as management of few. It is a matter of organization.
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.
main :: IO () main = runWebGUI $ \ webView -> do doc <- getDocument webView body <- findBody doc htmlElementSetInnerHTML body html firstNameInputField <- findInputElement doc "firstNameInput" lastNameInputField <- findInputElement doc "lastNameInput" fullNameInputField <- findInputElement doc "fullNameInput" (firstNameEvents, publisherFn) <- sync newEvent (lastNameEvents, publisherFn') <- sync newEvent elementOnkeyup firstNameInputField $ publishInputValues publisherFn elementOnkeyup lastNameInputField $ publishInputValues publisherFn' let fullNameEvents = firstNameEvents <> lastNameEvents sync $ listen fullNameEvents $ const $ do val <- htmlInputElementGetValue firstNameInputField val' <- htmlInputElementGetValue lastNameInputField htmlInputElementSetValue fullNameInputField $ val <> " " <> val' return ()
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 html = renderHtml content content :: Html content = do nameForm css css :: Html css = style $ toHtml C.css nameForm :: Html nameForm = section ! A.id "formContainer" $ form $ do labeledInputField "firstNameInput" "First Name" False labeledInputField "lastNameInput" "Last Name" False labeledInputField "fullNameInput" "Full Name" True labeledInputField :: AttributeValue -> Html -> Bool -> Html labeledInputField id_ label_ readonly = p $ do input ! A.type_ "text" ! A.maxlength "10" ! A.id id_ !? (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 css = unpack . render $ formCss formCss :: Css formCss = do formContainerCss formLabelCss inputCss advertisementCss formContainerCss :: Css formContainerCss = "#formContainer" ? do background niceGrey display inlineBlock border solid (px 1) "#CDCDCD" borderRadius (px 5) (px 5) (px 5) (px 5) margin (em 0.75) (em 0.75) (em 0.75) (em 0.75) padding (px 10) (px 10) (px 10) (px 10) inputCss :: Css inputCss = "input" ? do padding (px 9) (px 9) (px 9) (px 9) border solid (px 1) "#E5E5E5" outline solid (px 0) (rgba 0 0 0 255) fontFamily ["Verdana", "Tahoma"] [sansSerif] fontSize (px 12) background ("#FFFFFF" :: Color) boxShadow (px 0) (px 0) (px 8) (rgba 0 0 0 10) background $ linearGradient (straight sideTop) [ ("#ffffff", pct 0.1) , ("#eeeeee", pct 14.9) , ("#ffffff", pct 85.0) ] formLabelCss :: Css formLabelCss = "form label" ? do marginLeft (px 10) fontSize (px 13) color coolBreezeGrey advertisementCss :: Css advertisementCss = "#ad" ? do marginLeft (px 5) fontSize (px 10) fontStyle italic color coolBreezeGrey coolBreezeGrey :: Color coolBreezeGrey = "#999999" niceGrey :: Color niceGrey = "#F5F5F5"
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.
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.