Om and Clojurescript

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 43

High Performance Web UI's with

Om and React
LambdaJam - Brisbane, 2014

Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoughtworks.com

About
ThoughtWorker
Functional Programming & Clojure
advocate
Founder of the Sydney Clojure User
Group

Currently writing Clojure Reactive


Programming

Functional Programming
is on the rise

And that makes us happy

However if you do client-side web


development, youre out of luck

Because Javascript
[1,3,5].map(parseInt);!
// [1, NaN, NaN]!

There are options

Today well see one of them:


Clojurescript

What well see


An Overview of React and how it enables fast rendering
Meet Om, a ClojureScript interface to React
Boosting Reacts rendering performance with immutable data structures
A simple demo featuring data binding and undo functionality

React
Created by Facebook for building user interfaces
The V in MVC
Self-contained components
Doesnt make assumptions about your stack - can be used with anything

Self-contained components
React combines display logic and DOM generation
Components are themselves loosely coupled
The whole app is re-rendered on every update
Virtual DOM
Efficient diff algorithm

Efficient diff algorithm


Creates a virtual version of the DOM
As the application state changes new DOM trees are generated
React diffs the trees and computes the minimal set of changes
Finally it applies the changes to the real DOM

A simple React component


var HelloMessage = React.createClass({!
displayName: 'HelloMessage',!
render: function() {!
return React.DOM.div(null, "Hello ", this.props.name);!
}!
});!
!

React.renderComponent(!
HelloMessage( {name:"John"} ), mountNode);

No literals?

A simple React component (using the


JSX pre-processor)
var HelloMessage = React.createClass({!
render: function() {!
return <div>Hello {this.props.name}</div>;!
}!
});!
!

React.renderComponent(!
<HelloMessage name="John" />, mountNode);

React components are functions from


application state to a DOM tree

Now lets take a leap and look at the same


component, written in Om

A simple Om component
(def app-state (atom {:name "Leo"}))!
!

(defn hello-message [app owner]!


(reify om/IRender!
(render [_]!
(dom/div nil!
(str "Hello " (:name app))))))!
!
!

(om/root hello-message app-state!


{:target (. js/document (getElementById "hello"))})!

Om/Reacts component lifecycle


IRenderState
IInitState

IWillMount

IShouldUpdate
IRender

IShouldUpdate
Called on app state changes but before rendering
This is where React uses its fast diff algorithm
Om components implement the fastest algorithm possible: a simple
reference equality check
Generally, you wont have to implement this

IInitState & IRenderState


Initialise component local state using IInitState
Use IRenderState to work with it and render the component

IInitState & IRenderState


(defn counter [app owner]!
(reify!
om/IInitState!
(init-state [_]!
{:clicks 0})!
om/IRenderState!
(render-state [_ state]!
(dom/div nil!
(str "Clicks " (:clicks state))!
(dom/button #js {:onClick!
#(om/set-state! owner :clicks (inc (:clicks state)))}!
"Click me!")))))!
!

(om/root counter (atom {})!


{:target (. js/document (getElementById "app"))})!

IRender
Same as IRenderState
except it doesnt depend on the component local state to render

IRender
(def app-state (atom {:name "Leo"}))!
!

(defn hello-message [app owner]!


(reify om/IRender!
(render [_]!
(dom/div nil!
(str "Hello " (:name app))))))!
!
!

(om/root hello-message app-state!


{:target (. js/document (getElementById "hello"))})!

A larger example

A larger example

A reusable editable component


(defn editable [text owner]!
(reify!
om/IInitState!
(init-state [_]!
{:editing false})!
om/IRenderState!
(render-state [_ {:keys [editing]}]!
(dom/li nil!
(dom/span #js {:style (display (not editing))} (om/value text))!
(dom/input!
#js {:style (display editing)!
:value (om/value text)!
:onChange #(handle-change % text owner)!
:onKeyPress #(when (== (.-keyCode %) 13)!
(commit-change text owner))!
:onBlur (fn [e] (commit-change text owner))})!
(dom/button!
#js {:style (display (not editing))!
:onClick #(om/set-state! owner :editing true)}!
"Edit")))))!
From https://github.com/swannodette/om/wiki/Basic-Tutorial

A reusable editable component


(defn editable [text owner]!
(reify!
om/IInitState!
(init-state [_]!
{:editing false})!
om/IRenderState!
(render-state [_ {:keys [editing]}]!
(dom/li nil!
(dom/span #js {:style (display (not editing))} (om/value text))!
(dom/input!
#js {:style (display editing)!
:value (om/value text)!
:onChange #(handle-change % text owner)!
:onKeyPress #(when (== (.-keyCode %) 13)!
(commit-change text owner))!
:onBlur (fn [e] (commit-change text owner))})!
(dom/button!
#js {:style (display (not editing))!
:onClick #(om/set-state! owner :editing true)}!
"Edit")))))!
From https://github.com/swannodette/om/wiki/Basic-Tutorial

A reusable editable component


(defn editable [text owner]!
(reify!
om/IInitState!
(init-state [_]!
{:editing false})!
om/IRenderState!
(render-state [_ {:keys [editing]}]!
(dom/li nil!
(dom/span #js {:style (display (not editing))} (om/value text))!
(dom/input!
#js {:style (display editing)!
:value (om/value text)!
:onChange #(handle-change % text owner)!
:onKeyPress #(when (== (.-keyCode %) 13)!
(commit-change text owner))!
:onBlur (fn [e] (commit-change text owner))})!
(dom/button!
#js {:style (display (not editing))!
:onClick #(om/set-state! owner :editing true)}!
"Edit")))))!
From https://github.com/swannodette/om/wiki/Basic-Tutorial

The speakers view


(defn speakers-view [app owner]!
(reify!
om/IRender!
(render [_]!
(dom/div nil!
(dom/div #js {:id "speakers"!
:style #js {:float "left"}}!
(dom/h2 nil "Speakers")!
This is how you build
(dom/button
#js
{:onClick
undo}
"Undo")!
components
(dom/button #js {:onClick reset-app-state} "Reset")!
(apply dom/ul nil!
(om/build-all speaker-view (speakers app)!
{:shared {:app-state app}})))!
(om/build speaker-details-view app)))))

The Sessions view


(defn sessions-view [app owner]!
(reify!
om/IRender!
(render [_]!
(dom/div #js {:id "sessions"}!
(dom/h2 nil "Sessions")!
(apply dom/ul nil!
(map #(om/build editable %) (vals (:sessions
app))))))))!

Same deal as before

Apps can have multiple roots


(om/root speakers-view app-state!
{:target (. js/document (getElementById "speakers"))})!
!

(om/root sessions-view app-state!


{:target (. js/document (getElementById "sessions"))})!

You can have multiple mini-apps inside your main app


Makes it easy to try Om in a specific section/feature

What about undo and reset?

Implementing undo
(def app-state
(atom speaker-data))!
(def app-history (atom [@app-state]))!
!

(add-watch app-state :history!


(fn [_ _ _ n]!
(when-not (= (last @app-history) n)!
(swap! app-history conj n))!
(let [c (count @app-history)]!
(prn c " Saved items in app history"))))!
!

(defn undo []!


(when (> (count @app-history) 1)!
(swap! app-history pop)!
(reset! app-state (last @app-history))))!

Implementing reset
(defn reset-app-state []!
(reset! app-state (first @app-history))!
(reset! app-history [@app-state]))!

Om/React components are functions from state to


DOM trees

With immutable data structures we can access


every version of the application state

So we simply update the application state, causing


the components to get re-rendered

A bit of live coding

Summary
With Om, youre not using a crippled template language, you can
leverage all of Clojurescript (including other DOM libraries)
Rendering and display logic are inevitably coupled. Om/React
acknowledges that a bundles them in components
The whole app is re-rendered on every state change, making it easier to
reason about
This is efficient thanks to immutable data structures

Summary
Clojurescript also provides a better development experience with a
powerful browser REPL much like what youd get with Clojure on the JVM
Source maps are here today
Bottom line is that Clojurescript is a serious contender

References
Code: https://github.com/leonardoborges/lambdajam-2014-om-talk
React documentation: http://facebook.github.io/react/
Om documentation: https://github.com/swannodette/om/wiki/
Documentation#build
Basic Tutorial: https://github.com/swannodette/om/wiki/Basic-Tutorial

Thanks!
Questions?
Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoughtworks.com

You might also like