--- title: OM入門 tags: Clojure author: ponkore slide: false --- 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` から。まさに最小限のコードですね。 ````
```` 上記`
`のところに、次に示す`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 の1行を表す 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 ( (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)が非常にわかりやすいです) 以上になります。