OM入門


Web開発のクライアントサイドのフレームワークについては、これまで様々なものが流行っては廃れたりして、なかなか落ち着かない感じではありますが、今回ネタにとりあげる `om`([https://github.com/swannodette/om](https://github.com/swannodette/om))、どこまで盛り上がるのか興味深いところです。

om とは
=======

端的に言えば「Facebook の Client Side Framework である[React](https://github.com/facebook/react)の ClojureScript による wrapper」になります。

React は、MVC で言うところの View レイヤーという位置づけのFrameworkで、「通常の DOM より軽量な **Virtual DOM** を操作、Virtual DOM の差分を DOM に反映(パッチ当て)することで 高速なViewの更新を実現」しています。

Virtual DOM という考え方がどのくらい昔からあるかはよく知らないのですが、今年に入ってから頻繁に耳にする様になりました(React の影響でしょうか...)。

当然のように ClojureScript による wrapper が登場するわけですが、今のところ(というか自分が知っているだけですが) 2種類の Framework が存在します。

Reagent
-------
[Reagent](http://holmsand.github.io/reagent/) は、Virtual DOM の表現のために `hiccup` 形式の構文を標準装備しており、Clojure に慣れたプログラマには取っ付き易いように見えます。一時期開発が停滞しているのかな、と思っていましたが、Reagent 本体よりもその周辺(leiningen template とか tutorial) が充実してきており、馴染みやすさからすると Reagent から始めても良かったかもしれません(ちょっと後悔...)。

* [Reagent Cookbook](https://github.com/reagent-project/reagent-cookbook) - いわゆるサンプル集、こういうのがあるととっかかりやすい印象を受けます。
* [Reagent Template](https://github.com/reagent-project/reagent-template) - reagentを簡単に使える`leiningen template`。
* [Reagent Seed](https://github.com/gadfly361/reagent-seed) - こちらも`leiningen template`ですが、以下のものがごった煮で付いてきます。
	* [secretary](https://github.com/gf3/secretary) - client-side routing
	* [garden](https://github.com/noprompt/garden) - css を hiccup like な感じで使うライブラリ
	* [austin](https://github.com/cemerick/austin) - ClojureScript の browser REPL

om
--
[om](https://github.com/swannodette/om) は、Virtual DOM生成部分は他のライブラリに任せるスタンスです。 **コンポーネント** を作成するために組み合わせて使えるような機能をコンパクトにまとめた、といった感じでしょうか。Virtual DOM生成機能は当然標準でも用意されていますが、hiccup のような使いやすい抽象化されたものではないので、やはり他の Framework を組み合わせて使うのが本筋なようです。知っている範囲では、

* [sablono](https://github.com/r0man/sablono) - [hiccup](https://github.com/weavejester/hiccup) 的な構文を提供してくれるライブラリ
* [kioo](https://github.com/ckirkendall/kioo) - [enlive](https://github.com/cgrand/enlive) 的な感じのテンプレートライブラリ

が組み合わせて使えるとされています。

Reagent と om、どちらが主流だとか優位性があるとかいう話ではないと思いますが、今回は om について try してみました。

om 入門
=======

最速で Hello, world!
--------------------

さて、ここからが本番です。`leiningen` は導入済みであるとします(なるべく最新が良いと思います。

手っ取り早く`om`のアプリケーションを作成するために、とてもシンプルな `leiningen` テンプレートが提供されています(`mies-om`)。

早速使ってみます。

```` shell
    bash$ lein new mies-om mies-om-example
    Retrieving mies-om/lein-template/0.4.1/lein-template-0.4.1.pom from clojars
    Retrieving mies-om/lein-template/0.4.1/lein-template-0.4.1.jar from clojars
    bash$ cd mies-om-example/
    bash$ lein deps
    bash$ lein cljsbuild once
    Compiling ClojureScript.
    Compiling "mies_om_example.js" from ["src"]...
    Successfully compiled "mies_om_example.js" in 9.231 seconds.
    bash$ 
````

ここで `mies-om-example` ディレクトリ直下にある `index.html`をブラウザで開くと、以下のように表示されるはずです。

![sc2.png](https://qiita-image-store.s3.amazonaws.com/0/3208/611932ba-560d-bac9-de8c-9dd2eb2fff20.png "sc2.png")

`project.clj` には、cljs を開発用にコンパイルするための最小限の設定がされております(解説は省略)。まずは `index.html` から。まさに最小限のコードですね。

````
<html>
    <body>
        <div id="app"></div>
        <script src="http://fb.me/react-0.11.1.js"></script>
        <script src="out/goog/base.js" type="text/javascript"></script>
        <script src="mies_om_example.js" type="text/javascript"></script>
        <script type="text/javascript">goog.require("mies_om_example.core");</script>
    </body>
</html>
````
上記`<div id="app"></div>`のところに、次に示す`ClojureScript`により(Virtualではない)DOMが生成され埋め込まれます。

でその`ClojureScript`がこちらです(omの[Basic Tutorial](https://github.com/swannodette/om/wiki/Basic-Tutorial)の最初のコードと同じです)。

````clojure
;; src/mies_om_example/core.cljs
(ns mies-om-example.core
  (:require [om.core :as om :include-macros true]
            [om.dom :as dom :include-macros true]))

(enable-console-print!)

(def app-state (atom {:text "Hello world!"}))

(om/root
  (fn [app owner]
    (reify om/IRender
      (render [_]
        (dom/h1 nil (:text app)))))
  app-state
  {:target (. js/document (getElementById "app"))})
````

om では、アプリケーションの状態を `atom` として管理します(`app-state`)。`atom` の中身はClojureScriptで扱えるimmutableなデータであれば何でも良いです。この例では`:text`をキーにもつ`map`になっています。

`om/root` は、ざっくり言うと「アプリケーションの状態をみながら React 経由での描画(というかDOM構築・パッチ当て)を行う」イメージです。`om/root` は、以下の引数を取ります。

````clojure
(defn root
  ([f value options] ...))
````

* *f* : `IRender`、`IRenderState`を`reify`したインスタンスを返す関数を指定します。`IRender`、`IRenderState`は om のプロトコルで、Virtual DOM を構築して返すようにします。つまりここが **View**の中核となるところです。
* *value* : Virtual DOMに対応する ClojureScript の tree 構造をもつデータ、もしくは `atom` で wrap した tree 構造をもつデータを指定します。通常は動的な View を構成
するためにこのフレームワークを使うので、サンプルでも無い限り`atom`でwrapしたデータ構造を指定することになります。
* *options* : `om.core/build` 関数に渡すオプションの`map`を指定します。`:target` が必須で `om/root` で構築されたコンポーネントを設定(mount) する先となる JavaScript の element を指定します。`target`以外のオプションについては [omのドキュメント](https://github.com/swannodette/om/wiki/Documentation#root) を参照してください。

これだけでは動きが無いためよくわからないので、少し動きのあるものを作ってみます。

Next Step (contact list)
------------------------

次に示す例は、[omのチュートリアル](https://github.com/swannodette/om/wiki/Basic-Tutorial)を少しモディファイしたもの(Contact List)です。まずはイメージから。`css framework`は個人的に時々使ってる[Ink](http://ink.sapo.pt/)を使っています(レイアウトがガタガタでカッコ悪いです...すみません)。

![sc1.png](https://qiita-image-store.s3.amazonaws.com/0/3208/561f5757-cff2-ac59-cdf2-ab574f5802ed.png "sc1.png")

### 管理する状態

* 今回はatomに包まれたmapを管理します。

````clojure
(def app-state
  (atom
   {:contacts
    [{:name "Ben"}
     {:name "Alyssa"}
     {:name "Eva"}
     {:name "Louis"}
     {:name "Cy"}
     {:name "Lem"}]}))
````

### contact-view 関数 (Contact List の１行を表す View)
* `IRenderState`プロトコルの関数`render-state`を実装します。今回は[sablono](https://github.com/r0man/sablono)を使って`hiccup`構文で書いてみました。
* `Delete`ボタンを押下したタイミングでのVirtual DOMの更新時には、`cljs.core.async/put!`を使っています。またそのための通知チャネルは後述するメインのviewの`IWillMount`プロトコルで実装しています。

````clojure
(defn contact-view [contact owner]
  (reify
    om/IRenderState
    (render-state [this {:keys [delete-chan]}]
      (html
       [:li
        [:span (:name contact)]
        [:button {:onClick (fn [e] (put! delete-chan @contact))
                  :class "ink-button red"} "Delete"]]))))
````

### contacts-view 関数 (Contacts List 全体の View)

````clojure
(defn contacts-view [app owner]
  (reify
    om/IInitState              ;; (1)
    (init-state [_]
      {:delete-chan (chan)
       :text ""})
    om/IWillMount              ;; (2)
    (will-mount [_]
      (let [delete-chan (om/get-state owner :delete-chan)]
        (go (loop []
              (let [contact (<! delete-chan)]
                (om/transact! app :contacts
                              (fn [xs] (vec (remove #(= contact %) xs))))
                (recur))))))
    om/IRenderState            ;; (3)
    (render-state [this state]
      (html
       [:div {:class "control-group"}
        [:h2 "Contact list"]
        (apply dom/ul nil (om/build-all contact-view (:contacts app)    ;; (4)
                                        {:init-state state}))
        [:div
         [:input {:type "text" :ref "new-contact" :value (:text state)  ;; (5)
                  :onChange #(handle-change % owner state)              ;; (6)
                  :placeholder "New Contact List Member"}]
         [:button {:onClick #(add-contact app owner)                    ;; (7)
                   :class "ink-button blue"
                   :disabled (empty? (:text state))} "Add contact"]]    ;; (8)
        [:div (:text state)]]))))                                       ;; (9)
````

* (1) *IInitState* : このView内部で使う状態を初期化します。ここでは Contact List に入力する文字列(`:text`)と、Deleteボタン押下時の通知に使う`cljs.core.async`のチャネル(`:delete-chan`)を設定しています。
* (2) *IWillMount* : このViewが mount される直前に一度だけ呼ばれるプロトコルです。`cljs.core.async/go` ループはここで実装します。この View では、子供の View である `contact-view` からの通知が来たら、 **状態の更新**、つまりDeleteボタンを押下した行のデータを削除(`remove`)しています。あくまで状態の更新、であって、DOMそのものは修正しない、ということがポイントです(ただしこの実装だと単純に名前が一致していたら消す、という動きなので、本当はもう少し考慮が必要です)。
* (3) *IRenderState* : Virtual DOM の構築を行うよう実装します。行の追加や削除等「操作に対する処理」は全く書かず「状態のあるがままを反映する」だけなので、記述は非常に楽になります。
* (4) contacts-view の各行は、`contact-view` 関数で定義していたので、ここでは [om/build-all](https://github.com/swannodette/om/wiki/Documentation#build-all) 関数を呼び出すことでその内容を展開しています。contacts-view が親、contact-viewが子、になるわけです。
* (5) `:ref "new-contact"` というのは、後述する処理でHTML`input`要素を参照する必要があるため、参照するための名前として定義してあります。
* (6) `:onChange`は`input`要素の`onChange`イベント発火時に呼ばれる関数を定義します。ここは普通に ClojureScript の関数をシームレスに書けるので、コードがすっきりして良いです。
* (7) Add contact ボタン押下時には後述する`add-contact`関数を呼びます。
* (8) テキスト入力欄が空欄の時にはボタンを押下できないようにしてみました(disabled=true)。
* (9) テキスト入力に連動して画面が動いてくれるか、確認するために`:text`の内容を表示してみました。

### add-contact 関数

````clojure
(defn add-contact [app owner]
  (let [new-contact (-> (om/get-node owner "new-contact") .-value)]       ;; (1)
    (when-not (empty? new-contact)
      (om/transact! app :contacts #(conj % (assoc {} :name new-contact))) ;; (2)
      (om/set-state! owner :text ""))))                                   ;; (3)
````
* (1) 前述(contacts-view 関数の(5)) で定義した `:ref` をここで使っています。`om/get-node` を使って実際のDOM要素にある`value`を`new-contact`として取得しています(つまり、管理している状態に反映される前に、画面から直接値を取得する、というわけです)。
* (2) contact-list にデータを追加します。ここでもあくまで **状態の更新** をしているだけであって、画面の更新は一切ここでは行っていないというところがポイントです。
* (3) 追加が終わったらテキスト入力欄をクリアするため、`:text` を空白にしています。

### handle-change 関数

````clojure
(defn handle-change [e owner {:keys [text]}]
  (let [value (.. e -target -value)]
    (if-not (re-find #"[0-9]" value)
      (om/set-state! owner :text value)
      (om/set-state! owner :text text))))
````
* 詳しく説明しませんが、数値は入力できないようにしてみました。

### 以下実際の動き

* テキスト入力欄に`a`と入力したところ。テキスト入力欄の下にも入力された文字が反映されています。また、一文字目が入力された時点で`Add contact`ボタンが enable になっています。

![sc3-2.png](https://qiita-image-store.s3.amazonaws.com/0/3208/015ca46c-9eaf-440b-8571-f5ad87571c05.png "sc3-2.png")

* `a`を入力後`Add contact`ボタンを押下したところ。上の`Contact List`に行が追加されました。またテキスト入力欄はクリアされ、`Add contact`ボタンは再び disable になりました。

![sc4.png](https://qiita-image-store.s3.amazonaws.com/0/3208/3056c095-7b35-f92e-2466-e85595bb284e.png "sc4.png")

* `a`の上の行にあった Lem の`Delete`ボタンを押下したところ。Lemの行がなくなりました。

![sc5-2.png](https://qiita-image-store.s3.amazonaws.com/0/3208/df94d0ed-b561-75de-36c1-a03d2adb9c2f.png "sc5-2.png")

全部まとめたソースを [github](https://github.com/ponkore/om-tut) にあげておきました。`git clone` して`lein cljsbuild once`し、`index.html`を開いて実際に動きを確かめてみていただければ、と思います。

まとめ
======

今回触れなかったことはいっぱいあります。

* om の用語としての`Cursor`とか`Lifecycle Protocol`とか..
* sablono のかわりに kioo 使ってみる、とか..
* React から SVG いじるとか普通にできるので、グラフィカルな感じのもやってみたい、とか..
* clientとserverを連携してみる、とか..
* もっと実用的な画面(画面遷移とか含め)でサンプル作ってみたり、とか..
* [figwheel](https://github.com/bhauman/lein-figwheel)使うとClojureScriptの変更が即座にブラウザに反映されるので、開発が超ラクチンになるよ、とか..
* client side routing、とか..

やり残したことはいっぱいあるけど、今後のお楽しみということで、おいおい勉強していきたいと思います。

感想
---
* ClojureScript いい感じ。ちょっとずつ慣れてきたのでもっと勉強したくなった。
* 来年は React がもっと流行ると思う(Virtual DOM更新だけで良い、のはすごく楽)。

see also
--------
* [http://yogthos.net/posts/2014-12-1-State-of-Reagent.html](http://yogthos.net/posts/2014-12-1-State-of-Reagent.html)(英語)
* [minikomi](http://qiita.com/minikomi)さんの om 記事[#0](http://qiita.com/minikomi/items/c77fea29f63e509a05c5)、[#0.5](http://qiita.com/minikomi/items/c5b515f35e6818a828e3)、[#1](http://qiita.com/minikomi/items/23c4069425b4ac411b63)、[#2](http://qiita.com/minikomi/items/770492db2aca0f6d3757)。参考にさせていただきました。
* om についての [InfoQの記事](http://www.infoq.com/jp/news/2014/02/om-react)。
* [Virtual DOM Advent Calendar 2014](http://qiita.com/advent-calendar/2014/virtual-dom)
* [一人React.js Advent Calendar 2014](http://qiita.com/advent-calendar/2014/reactjs)
* [WebFUI](https://github.com/drcode/webfui) - `ClojureScript`でVirtual DOMと似たようなコンセプトで作られたクライアントサイドフレームワーク。残念ながら開発が止まっているようです。(id:ymbpc さんの[記事](http://ympbyc.hatenablog.com/entry/20130607/1370622001)が非常にわかりやすいです)

以上になります。