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.
<iframe src="/static/news/bindings-tutorial.js_.jsexe/index.html" sandbox="allow-scripts" scrolling="no" frameborder=0 width=345 height=240 style="float: right;"> </iframe>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 ()
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 ()
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
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.
—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.