はじめに
おはようございます。当ブログにアクセス頂き、ありがとうございます。昨日美味しい生牡蠣を腹一杯食しご機嫌の、たなけんです。
本エントリでは、Clojurescriptを利用したWebクライアント開発について記載します。
Clojurescriptとは
Clojurescriptとは、ClojureでJavaScriptアプリケーションを開発するためのライブラリ群です。CoffeeScriptやJSX同様、書かれたプログラムをJavaScriptにコンパイルすることによって、アプリケーションをJavaScriptが動作する環境(webブラウザやnode.jsなどのサーバ上)で動作させることができます。なぜClojurescriptなのか
JavaScriptの言語仕様(変数スコープ、プロトタイプ方式オブジェクト指向など)に馴染めないこと、Clojureスタイルでのプログラミングが私にとって最も効率が良いということからClojurescriptを利用することにしました。
また、ClojurescriptはGoogle Closure Toolsを利用しているため、Closureが提供するクラス方式のオブジェクトシステムや、多彩なUIコンポーネントが利用できる点も魅力でした。
Clojurescriptの導入
ひなたねこさんの記事を参考にlein-cljsbuildを導入しました。lein cljsbuild auto としておけば、core.cljsを更新したタイミングでJavaScriptにコンパイルされます。(他のツールは必要なく、lein-cljsbuildだけで完結している点が素晴らしいです)また、emacsのClojurescript-modeはELPA経由でインストールしました。
Clojurescript用のreplもemacsから利用可能な様でしたが、今回は出力されたJavaScriptを見てデバッグを行いました。(時間を見つけて、Clojurescriptのreplも試そうと思います)
Webクライアントの実装(1)
プロジェクトファイル
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defproject cljstest "0.1.0-SNAPSHOT" | |
:description "FIXME: write description" | |
:url "http://example.com/FIXME" | |
:license {:name "Eclipse Public License" | |
:url "http://www.eclipse.org/legal/epl-v10.html"} | |
:dependencies [[org.clojure/clojure "1.4.0"]] | |
:plugins[[lein-cljsbuild "0.2.4"]] | |
:cljsbuild { | |
:builds [{ | |
:source-path "src" | |
:compiler { | |
:output-to "main.js" | |
:pretty-print true}}]}) |
プラグイン部でlein-cljsbuildの利用を宣言します。また、closure compilerで用いるコンパイルオプションをcljsbuild部で指定します。今回は生成されるJavascriptの可読性を重視したため、最適化はしない方針としました。
index.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<title>cljstest</title> | |
<script type="text/javascript" src="out/goog/base.js"></script> | |
<script type="text/javascript" src="main.js"></script> | |
</head> | |
<body> | |
<input id="query-word" value="" type="text" /> | |
<button id="dictionary-button" type="submit">send</button> | |
<div id="result"></div> | |
<script>cljstest.core.main();</script> | |
</body> | |
</html> |
ヘッダ部でJavaScriptファイルを指定し、ボディ部の最後でmain関数を呼び出しています。また検索語を入力するフォーム、確定ボタン、結果表示用のdiv要素を宣言しました。
nsマクロ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my-dictionary-client.core | |
(:require [clojure.browser.event :as event] | |
[clojure.browser.dom :as dom] | |
[goog.net.XhrIo]) |
通常のClojureとの違いとして、ClojureからJavaのクラスを利用する場合は:requireではなく:importを使用するのですが、ClojurescriptからClosure Libraryのクラスを利用する場合もClojureのライブラリ同様:requireで良いという点があります。
nsマクロでは、以下のライブラリの利用を宣言しました。
- clojure.browser.event: ブラウザのイベントを操作するClosure Libraryのクラス群の薄いラップしたライブラリ
- clojure.browser.dom: ブラウザのDOMを操作するClosure Libraryのクラス群の薄いラップしたライブラリ
- goog.net.XhrIo: Closure Libraryが提供するJacascriptのhttpXmlRequestオブジェクトを操作するクラス
main関数
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn main | |
[] | |
(event/listen (dom/get-element :dictionary-button) "click" dictionary-request)) |
確定ボタンのクリックイベントをトリガーにdictionary-request関数を呼びます。
- clojure.browser.event/listen関数: 引数に指定された要素、イベントによって第3引数で指定された関数が呼び出されるよう設定
- clojure.browser.dom/get-element関数: 引数に指定されたシンボルのidに合致する要素を取得
生成されたJavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cljstest.core.main = function main() { | |
return clojure.browser.event.listen.call(null, clojure.browser.dom.get_element.call(null, "\ufdd0'dictionary-button"), "click", cljstest.core.dictionary_request) | |
}; |
dictionary-request関数
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn- dictionary-request | |
[e] | |
(goog.net.XhrIo/send | |
(str "http://localhost:3000/dictionary/" (dom/get-value (dom/get-element :query-word))) | |
dictionary-callback)) |
httpのGETメソッドによるリクエストを送信し、結果を引数にコールバック関数を呼び出します。
- goog.net.XhrIo/sendメソッド: 引数のurlにhttpのGETメソッドによるリクエストを送信
- clojure.browser.dom/get-value関数: 引数に指定された要素の値(今回の場合、フォームquery-wordに入力された文字列)を取得
生成されたJavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cljstest.core.dictionary_request = function dictionary_request(e) { | |
return goog.net.XhrIo.send([cljs.core.str("http://localhost:3000/dictionary/"), cljs.core.str(clojure.browser.dom.get_value.call(null, clojure.browser.dom.get_element.call(null, "\ufdd0'query-word")))].join(""), cljstest.core.dictionary_callback) | |
}; |
dictionary-callback関数
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn- dictionary-callback | |
[e] | |
(let [entries (.-entries (.getResponseJson e/target))] | |
(dom/append (dom/get-element :result) (create-list entries)))) |
httpレスポンスからJSONオブジェクトを取得し、create-list関数を用いて結果を変換し、変換された結果をdiv要素の子要素として追加します。
- .-プロパティ名 特殊形式: JavaScriptオブジェクトのプロパティにアクセス(今回の場合entriesプロパティ)
- goog.net.XhrIo/getResponseJsonメソッド: 取得されたレスポンス内のデータをJSONオブジェクトとして取得
- clojure.browser.dom/append関数: 第1引数に指定された要素に、第2引数に指定された要素を子要素として追加
生成されたJavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cljstest.core.dictionary_callback = function dictionary_callback(e) { | |
var entries__212463 = e.target.getResponseJson().entries; | |
return clojure.browser.dom.append.call(null, clojure.browser.dom.get_element.call(null, "\ufdd0'result"), cljstest.core.create_list.call(null, entries__212463)) | |
}; |
create-list関数
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn- create-list | |
[entries] | |
(dom/html->dom | |
(str "<ul>" | |
(apply str (for [entry entries] (str "<li>" (.-text entry) "</li>"))) | |
"</ul>"))) |
JSONオブジェクトを引数に取り、textプロパティから値を取得し、<ul><li>textプロパティの値</li> ... </ul>形式のdom要素を作成します。
- clojure.browser.dom/html->dom関数: html形式の文字列からDOMツリーを生成
生成されたJavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cljstest.core.create_list = function create_list(entries) { | |
return clojure.browser.dom.html__GT_dom.call(null, [cljs.core.str("<ul>"), cljs.core.str(cljs.core.apply.call(null, cljs.core.str, function() { | |
var iter__2482__auto____212461 = function iter__212455(s__212456) { | |
return new cljs.core.LazySeq(null, false, function() { | |
var s__212456__212459 = s__212456; | |
while(true) { | |
if(cljs.core.seq.call(null, s__212456__212459)) { | |
var entry__212460 = cljs.core.first.call(null, s__212456__212459); | |
return cljs.core.cons.call(null, [cljs.core.str("<li>"), cljs.core.str(entry__212460.text), cljs.core.str("</li>")].join(""), iter__212455.call(null, cljs.core.rest.call(null, s__212456__212459))) | |
}else { | |
return null | |
} | |
break | |
} | |
}, null) | |
}; | |
return iter__2482__auto____212461.call(null, entries) | |
}())), cljs.core.str("</ul>")].join("")) | |
}; |
意外な落とし穴
実装を終えて早速クライアントからサーバへの疎通を確認しようと、ブラウザからアクセスしたところ、Safariでは問題なくレスポンスが処理されるが、Firefoxではレスポンスのボディ部が0バイト(サーバ側で設定したはずの値が、ボディ部に含まれていない)様な挙動を見せ、想定した通りの処理をすることができませんでした。ブラウザ毎に異なる挙動はClosureライブラリで吸収されているはず、と思いながらも、ここから問題解決まで思ったよりも時間が掛かってしまいました。。。(次回に続く)
今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん (作業時間90分)
0 件のコメント:
コメントを投稿