Anatomy of a Om component

Om is a Clojurescript library to represent UIs[1]. It interfaces with React.js, which is what Facebook built and is using to make their app. By using a efficient algorithm for computing DOM Diffs, inspired by Doom 3's game engine render loop, the complexity of keeping track of app state is reduced significantly without performance costs. This is a very brief overview of a Om component.

There are two main parts to a app written with Om. One is the app state, which is what it sounds like. In a todo app this would be the todos, whether they are done or not, and if a specific todo is being edited right now, along with any other attributes needed to keep track of the current application state. The other is the component, a view if you will, which represents the UI and is automatically re-rendered on every requestAnimationFrame. Depending on the app state or any action the user takes the UI might look differently, and this is what we control in the component. Components are just functions, and they can be built up by other components.

Let's have a look at a non-trivial example of a Om component. These come from TodoMVC example app [2,3] by David Nolen (creator of Om, along with many other awesome Clojure libraries). This component represents one specific todo.

(defn todo-item [todo owner]
  (reify
    om/IInitState
    (init-state [_]
      {:edit-text (:title todo)})

    om/IDidUpdate
    (did-update [_ _ _ _]
      (when (and (:editing todo)
                 (om/get-state owner :needs-focus))
        (let [node (om/get-node owner "editField")
              len  (.. node -value -length)]
          (.focus node)
          (.setSelectionRange node len len))
        (om/set-state! owner :needs-focus nil)))

    om/IRenderState
    (render-state [_ {:keys [comm] :as state}]
      (let [class (cond-> ""
                    (:completed todo) (str "completed")
                    (:editing todo)   (str "editing"))]
        (dom/li #js {:className class :style (hidden (:hidden todo))}
          (dom/div #js {:className "view"}
            (dom/input
              #js {:className "toggle" :type "checkbox"
                   :checked (and (:completed todo) "checked")
                   :onChange (fn [_] (om/transact! todo :completed #(not %)))})
            (dom/label
              #js {:onDoubleClick #(edit % todo owner comm)}
              (:title todo))
            (dom/button
              #js {:className "destroy"
                   :onClick (fn [_] (put! comm [:destroy @todo]))}))
          (dom/input
            #js {:ref "editField" :className "edit"
                 :value (om/get-state owner :edit-text)
                 :onBlur #(submit % todo owner comm)
                 :onChange #(change % todo owner)
                 :onKeyDown #(key-down % todo owner comm)}))))))

There are three protocols that are satisfied in this function. There's only one function that is implemented for each protocol [4]. IInitState has a function init-state which is a map describing the initial state. The state for a specific todo item is that we are editing its title. It's only called once in the beginning of the component's life.

The second protocol, IDidUpdate, has a function did-update that takes four arguments: this, prev-props (previous application state belonging to the component), prev-state (previous component state), and root-node (the actual DOM node associated with the component). In this case we are not directly using any of them, hence the underscores. The function is called every time the component has been rendered in the DOM. In this case, when the owner of a todo-item (the todo-app component, which consists of todo-items) has :needs-focus set and we are editing the current todo, then we focus on the todo-item so the user can edit it. The main functions from om which are used is the get-state and set-state! functions, which is exactly what it sounds like, where the state is stored in a application state atom.

The third and final protocol IRenderState and the function render-state is the meat of the component. There are two main protocols that can be used for rendering, either IRender or IRenderState. The only difference is that IRenderState's function takes a state argument, which is a map. It specifies all the DOM elements and various event handlers (submit, change, key-down, edit) which are just functions. comm here is a core.async (another awesome library for dealing with concurrency) channel, which we use to communicate state changes asynchronously with the function put!. The function om/transact! changes the DOM tree at the specified cursor (the position in the DOM tree, in this case the todo) by applying the function specified in the second argument, and triggers a re-render. This results in a direct toggling of whether a todo is completed or not, without updating the rest of the DOM, i.e. it is very responsive and seamless.

There are on the order of 10 protocols, which correspond to different times during the life cycle of a DOM. You can read more about them in the Om documentation, along with some more detail in the React.js documentation. Some are specific to Om, but most are not.

This has been a very brief overview of one specific component. If you want to learn more about Om, I suggest you check out the official repository, which houses a good tutorial and documentation. I also highly suggest you read David Nolen's post on the future of javascript MVCs [5], which is where he introduced Om.