-
Notifications
You must be signed in to change notification settings - Fork 50
Om Tutorial
This is a WIP
This tutorial is adapted from DNolen's Om tutorial but is optimized for usage with Counterclockwise.
Create a new project named om-tut
with the File > New > Clojure Project
wizard:
- type
om-tut
in theProject name:
field, - type
mies-om
in theLeiningen template:
field.
This will create a project called om-tut
in the Package Explorer View
. Select the om-tut
node, and then type Alt+L L
to invoke the generic Leiningen launcher.
- Replace the selected text
<type>
withcljsbuild auto om-tut
- the whole popup's text should then contain the text
om-tut $ lein cljsbuild auto om-tut
. - Type
Enter
and wait a while for Leiningen to start and the popup to disappear.
This will start auto building so that recompiles will occur when you
save a file. The first build will take a few seconds. Once the build
has succeeded open index.html
in your favorite browser (we recommend
Google Chrome as it has excellent support for source maps). You can do this from Eclipse by right clicking on it and choosing Open with ... > System Editor
. You should
see an h1
tag with the text content Hello World!
in it.
Open src/om_tut/core.cljs
. In Eclipse you can type Ctrl+Shift+R
(Cmd+Shift+R
on a Mac) to open a R
esource, and then type the file name (here core.
) to filter the resources list. Change :text
value of
app-state
to be something else other than Hello World!
. Save the
file. Refresh your browser and you should see the new contents.
That's pretty boring isn't it? Let's do some live coding instead.
Type the key chord Control-SPACE
to open up the command list. Start
typing Add Connection
, press enter to select it. In the list of
options select Browser (External). Copy and paste the script tag
into index.html before the <div id="app"></div>
.
Open the JavaScript Console. You can do this via the Chrome menu
selection View > Developer > JavaScript. Refresh your browser, if
everything went well you should see XHR finished loading ...
in the
console. Now arrange your windows so that you can see both the Chrome
window and your source code at the same time.
Now at the bottom of the src/om_tut/core.cljs
source file write the
following:
(swap! app-state assoc :text "Do it live!")
Place your cursor at the end of the expression and type the key chord
Command-ENTER
to evaluate it. Again it will take a second to make the
initial connection. After the connection is made and the application
is updated, edit the string again, re-evaluate and you will see that
updating the application on the fly is pretty snappy.
Before proceeding remove the swap!
expression.
In Om the application state is held in an atom
, the one reference
type built into ClojureScript. If you change the value of the atom via
swap!
or reset!
this will always trigger a re-render of any Om
roots attached to it (we'll explain this in a second). You can think
of this atom as the database of your client side
application. Everything in the atom should be an associative data
structure - either a ClojureScript map or indexed sequential data
structure such as a vector.
om.core/root
(which is aliased to om/root
here), establishes a
Om rendering loop on a specific element in the DOM. The om.root
expression in the tutorial at this point looks like this:
(om/root
app-state
(fn [app owner]
(dom/h1 nil (:text app)))
(. js/document (getElementById "app")))
om.core/root
is idempotent, that is, it's safe to evaluate it
multiple times. It takes up to four arguments, but we're only
interested in the three argument case. The first argument is the
application state atom. The second argument is a function that takes
the application state data and the backing React component, here
called owner
. This function must return an Om component, a React
component, or some other value that React itself knows how to
render. The third argument is the target DOM node.
There can be multiple roots. Edit the index.html
, replace <div id="app"></div>
with the following:
<div id="app0"></div>
<div id="app1"></div>
And edit src/om_tut/core.cljs
replacing the om/root
expression
with the following:
(om/root
app-state
(fn [app owner]
(dom/h1 nil (:text app)))
(. js/document (getElementById "app0")))
Refresh your browser. You should see just one h1
tag on the
page. Copy and paste the om/root
expression and edit the second
one to look like the following:
(om/root
app-state
(fn [app owner]
(dom/h1 nil (:text app)))
(. js/document (getElementById "app1"))) ;; <-- "app0" to "app1"
Place your cursor at the end of this expression and evaluate it. You
should see the second h1
tag magically appear.
At the end of the file type the following and evaluate it.
(swap! app-state assoc :text "Multiple roots!")
You should see both h1
tags update on the fly. Multiple roots are
fully supported and synchronized to render on the same
requestAnimationFrame
.
Before proceeding remove the <div id="app1"></div>
from
index.html
and remove the second om/root
expression and the
swap!
expression. Save and refresh the browser.
Change the app-state
expression to the following and evaluate
it. Don't bother refreshing,
John McCarthy
would be pissed!
(def app-state (atom {:list ["Lion" "Zebra" "Buffalo" "Antelope"]}))
Change the om/root
expression to the following and evaluate it, you
should see a list of animals now.
(om/root
app-state
(fn [app owner]
(apply dom/ul nil
(map (fn [text] (dom/li nil text)) (:list app))))
(. js/document (getElementById "app0")))
You might have noticed that the first argument to dom/ul
and
dom/li
is nil
. This argument is how you set DOM attributes. Change
the om/root
expression to the following and evaluate it:
(om/root
app-state
(fn [app owner]
(apply dom/ul #js {:className "animals"}
(map (fn [text] (dom/li nil text)) (:list app))))
(. js/document (getElementById "app0")))
If you right click on the list in Google Chrome and select Inspect
Element you should see that the ul
tag in the DOM does indeed have
its CSS class attribute set to "animals".
#js {...}
and #js [...]
is what is referred to as a reader
literal. ClojureScript supports data literals for JavaScript via
#js
. #js {...}
is for JavaScript objects:
#js {:foo "bar"} ;; is equivalent to
#js {"foo" "bar"}
#js [...]
is for JavaScript arrays:
#js [1 2 3]
The #js
reader literal support is shallow, take note of the
following:
#js {:foo [1 2 3]} ;; a JS object with a persistent vector in it
In Om you have the full power of the ClojureScript language when building your user interface. At the same time, Om leaves the door open for alternate syntaxes for describing the DOM if that's your cup of tea.
Let's edit our code so we get zebra striping on the list. Lets add a
helper function stripe
before the om/root
expression:
(defn stripe [text bgc]
(let [st #js {:backgroundColor bgc}]
(dom/li #js {:style st} text)))
Don't forget to evaluate it!
Then change the om/root
expression to the following and evaluate it:
(om/root
app-state
(fn [app owner]
(apply dom/ul #js {:className "animals"}
(map stripe (:list app) (cycle ["#ff0" "#fff"]))))
(. js/document (getElementById "app0")))
As we can see ClojureScript offers powerful functional tools that put most templating languages completely to shame.