はじめに
みんな大好き、ClojureでWebアプリのお話です。
Clojrueのテンプレートとかよくわからなくても、HTMLじゃなくてJSONを返すようにすれば、JSと組み合わせてカッコイイサイトが簡単にできちゃいますね!
今回は、はやりのd3.jsを使ってみました。
注:Leiningenプロジェクトの作成とかd3.jsの設置といった基本的な説明は省きますm(_ _)m
環境
- clojure 1.6.0
- Leiningen 2.4.2
- Java 1.8.0
基本構成
hoge-app
├── project.clj ①依存ライブラリ
├── resources ④静的リソースを配置
└── public
└── assets
└── css
└── hogehoge.css
├── demo
│ └── hogehoge.html ⑤JSONを受け取るd3
└── index.html
└── src
└── hoge_app
├── core.clj ②ルーティングとか
└── hogehoge.clj ③マップを返すクラス
解説
①使用するライブラリなど
:dependencies [[org.clojure/clojure "1.6.0"]
[ring/ring-core "1.3.0"]
[ring/ring-jetty-adapter "1.3.0"]
[compojure "1.1.8"]
[cheshire "5.3.1"]]
:dev-dependencies [[ring/ring-devel "1.3.0"]]
:plugins [[lein-ring "0.8.11"]]
:main hoge-app.core
:ring {:handler hoge-app.core/app}
- ring
- RubyのRackに相当するWebアプリケーションライブラリセット
- ring-jetty-adapter
- leinコマンドからringサーバーを起動できる
- compojure
- ring対応のルーティングライブラリ
- cheshire
- JSONエンコーダー
②ルーティング、JSON変換、エントリポイントなど
(ns hoge-app.core
(:use [compojure.core]
[ring.util.response]
[ring.middleware.content-type]
[ring.middleware.params])
(:require [ring.adapter.jetty :as jetty]
[cheshire.core :as json]
[compojure.route :as route]
[compojure.handler :as handler]
[hoge-app.hogehoge :as hogehoge]))
(defn json-response [data & [status]]
{:status (or status 200)
:headers {"Content-Type" "application/json"}
:body (json/generate-string data)})
(defroutes api-routes
(GET "/demo/d3/hogehoge" []
(json-response (hogehoge/return-map))) ;---①
(route/resources "/") ;---②
(route/files "/demo/" {:root "demo"}) ;---③
(route/not-found "Page not found"))
(defn wrap-dir-index
[handler]
(fn [req]
(handler (update-in req [:uri] #(if (= "/" %) "/index.html" %)))))
(def app (-> api-routes
wrap-params
wrap-content-type
wrap-dir-index
handler/site))
(defn -main
[& args]
(let [port (or (first *command-line-args*) 8080)]
(jetty/run-jetty app {:port port})))
- json-response
- clojureのマップを受け取って、JSON形式のレスポンスを返す関数
cheshireのgenerate-stringメソッドで、マップをJSONに変換する - api-routes
- ルーティングの設定。
①はJSONを得るリクエストの設定。hogehogeクラスのreturn-mapメソッドを"/demo/d3/hogehoge"にルーティング(メソッドの戻り値は上記のjson-responseでJSON化)。
②は静的リソースのルート。
③で/demo/以下のファイルアクセスを可能にする - wrap-dir-index
- "/"でindex.htmlを表示できるようにする(今回の主題とはあまり関係ない…)
- app
- threadマクロで上記関数群をまとめる
- -main
- エントリポイント
③マップを返すhogehogeクラス
(ns hoge-app.hogehoge)
(def sample-data [["hoge" [["1/1/1" 10 ]
["2/2/2" 20 ]]
["piyo" [["3/3/3" 30]
["4/4/4" 40 ]]])
(defn make-map-seq [data]
(map #(array-map :age (get % 0) :power (get % 1)) data))
(defn merge-to-map [data]
(apply merge (map #(array-map (keyword (get % 0)) (make-map-seq (get % 1))) data)))
(def hogehoge-map (merge-to-map sample-data))
(defn return-map []
hogehoge-map)
ここはただの例です。本題からちょっと脱線します!!
最終的にマップを返せば良い。
ここでは、
- 多次元配列のsample-dataを、
- make-map-seqでマップ配列に変換して、
- merge-to-mapで最終的な形式のマップに統合している
(この辺、もっとスマートなコードにしたいので、親切な方アドバイスいただけないでしょうか…)
最終的には、
{ :hoge [{:age "1/1/1" :power 10} {:age "2/2/2" :power 20}]
:piyo [{:age "3/3/3" :power 30} {:age "4/4/4" :power 40}]
}
といった形のマップになる
④静的リソースの設置
②のルーティングに従って、resources/public下にhtml,js,css等を設置する
d3データ表示用のHTMLは、resources/public/demo下に設置する
⑤d3にJSONを渡し、グラフを描画する
グラフを描画…と言っても、d3の解説まで行うのは面倒なので、d3によるグラフ描画の詳細はソースをご参照ください。
https://github.com/circularuins/circularuins/blob/master/resources/public/demo/sentou_d3.html
ここでは、肝心のd3にレスポンスのJSONを渡すところだけ解説します。
…といっても超簡単です。
以下のルーティングで設定した、JSONを取得するクエリ、
(defroutes api-routes
(GET "/demo/d3/hogehoge" []
(json-response (hogehoge/return-map)))
この"/demo/d3/hogehoge"を、d3.jsonメソッドに渡してやるだけです。
<head>
<link rel="stylesheet" type="text/css" href="../assets/css/hogehoge.css">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script type="text/javascript">
d3.json("/demo/d3/hogehoge", function(error, data) {
// 以下でdataを処理
}
</script>
</body>
渡されたJSONデータはd3側でdataオブジェクトとして利用可能になります。
おしまい
以上でおしまいです。
わざわざclojureでやる意味あるの?って聞かれたら……ですが、rubyとかと遜色ない手軽さでWebアプリ書けますよ、ってことと、表示の部分はJSに任せちゃえばいいよね、って話でした。
グラフのデモは、
http://warm-fortress-5777.herokuapp.com/demo/show
こちらになります。
参考
http://stefan.arentz.ca/clojure-angularjs-recipes-demo.html