haXe/Neko でにゃんにゃん (其の2) JavaScriptアプリケーションを作る

前回 は haxe でコードを書き、JavaScript と Neko 二つの実行環境で nyan と言わせる所まで進みました。
今回は JSONP でその場検索する JavaScript を作ってみましょう。

本日のメニューです。

  • haxe で DOM Scripting
  • 型とキャスト
  • JSONP リクエストの生成
  • レスポンスの表示


ソースコード(完成)

haxe で DOM Scripting

アプリケーションの動作概要は

  • テキストボックスに検索語を入れて submit ボタンを押す
  • 検索結果をページ上にリスト表示する

といった所でしょうか。

まず submit ボタンを押したら何かアクションを起こさせるコードを作ってみましょう。

src/Application.hx

import js.Dom;
 
class Application {
    public static function main() {
        js.Lib.document.forms[0].onsubmit = function(evt:Event) {
            trace('hoge');
            return false;
        }
    }
}

コンパイルコマンドは haxe -cp src -main Application -js web/search_app.js です。
面倒なので build.hxml にまとめておきましょう。

build.hxml

-cp src
-main Application
-js web/search_app.js

こうしておくと haxe build.hxml でコンパイル出来ます。
JavaScript は意図した通りに動作していますか?

(もし 「evt : js.Event -> Void should be js.Event -> Bool」というエラーが出る場合は、return false; をキャストして return cast false; としてください。)

少し解説しましょう。
import js.Dom は haXe 標準ライブラリを呼んでいます。
ファイルでは /usr/lib/haxe/lib/std/js/Dom.hx に相当し、ドキュメントは file:///usr/lib/haxe/doc/index.html からたどれます。
(インストール先が異なる場合は上記パスも適当に読み替えてください)

js.Dom を import したので js.Dom.createElement .. なんて書き出すと「そんな field はねぇよ」とコンパイラにぶっちされます。
呼び出しには js.Lib.document .. と書きます。js.Dom で定義されたメソッドは不思議なチカラで js.Lib.document にマップされています。
js.Lib の下には他にも JavaScript でよく使う window, alert といった field が用意されています。

型とキャスト

では、trace(‘hoge’); の hoge に替えて値をテキストボックスから拾うようにしてみましょう。

        js.Lib.document.forms[0].onsubmit = function(evt:Event) {
            var query = js.Lib.document.getElementById('search_word');
            trace(query.value);
            return false;
        }
$haxe build.hxml
js.HtmlDom has no field value

怒られました。
工エエェ(゚Д゚)ェエエ工 value 無いの?

$grep -inr value std/js
td/js/Dom.hx:92:       var value : String;

あるようです。Dom.hx を見てみましょう。

typedef FormElement = {> HtmlDom,
 
	var disabled : Bool;
	var form : Form;
	var name : String;
	var type : String;
	var value : String;
 
	function blur() : Void;
	function click() : Void;
	function focus() : Void;
 
	var onblur : Event -> Void;
	var onclick : Event -> Void;
	var onfocus : Event -> Void;
}

HtmlDom 型を継承した FormElement 型に value field が定義されているようです。何となく DOM っぽい?
という事は、変数の型宣言をしてやると解決しそうです。

        js.Lib.document.forms[0].onsubmit = function(evt:Event) {
            var query : FormElement = cast js.Lib.document.getElementById('search_word');
            trace(query.value);
            return false;
        }

今度は通りました。
ブラウザをリロードして試してみてください。

型は haXe の強力な機能のひとつです。
型により、お手軽かつ lint よりも強力なエラー検知、さらにバグの少ないコードを書く事が出来ます。
ここの例で型だけ見ると「ウザい」と斬って捨てたくなりそうですが、これには標準ライブラリのデザインが練り足りない事、型を活かした haXe 流の開発技法を説明していないこと、その二点が影響しています。
また、型を気にせず記述する方法もありますが(untyped ブロック、__js__ 関数)、其の話はまたいずれ。

JSONP リクエストの生成

さて JSONP でどこかの検索エンジンに問い合わせてみます。
API Key 無しで使いたかったのでぐぐってぐぐって taggy.jp を使わせてもらうことにしました。

問い合わせを行うための request メソッドを、JSONP のコールバック用に displayResult メソッドを定義する事にします。

import js.Dom;
 
class Application {
 
    public static function main() {
        js.Lib.document.forms[0].onsubmit = function(evt:Event) {
            var query : FormElement = cast js.Lib.document.getElementById('search_word');
            if (query.value.length > 0) {
                request("http://taggy.jp/media/new/jsonp.do?encoding=UTF-8&media=blog&limit=2"
                    + "&callback=Application.displayResult"
                    + "&query=" + query.value
                );
            }
            return false;
        };
    }
 
    public static function request(query) {
        var scr = js.Lib.document.createElement('script');
        scr.setAttribute('src', query);
        js.Lib.document.getElementsByTagName('head')[0].appendChild(scr);
    }
 
    public static function displayResult(response) {
        trace(response);
    }
}

main メソッドを少し変えて検索語が空の時はリクエストを発行しないようにしました。
request メソッドは、、JSONP の手法そのままですね。
displayResult でレスポンスをダンプするようにしました。

コンパイルして実行します。
何かダンプされていますね。Entry, Count の文字列を含んだ JSON っぽい構造体がダンプされていれば成功です。
あとはこれをパース&再構築して完成です。もう一息。

レスポンスの表示

displayResult をいぢってやります。
Entry に含まれるエントリ内容をそれぞれリストアイテムに変換して DIV 要素 $(‘content’) に組込むとしましょう。

    public static function displayResult(response) {
        var list = js.Lib.document.createElement('ul');
        var tpl = new haxe.Template('<a href="::href::">[::title::]:<br /> ::description::</a>'); // template for list item
 
        // response.Entry -> template -> list item
        for (i in Reflect.fields(response.Entry)) { // fetch Entry.* fields
            var item = js.Lib.document.createElement('li');
            item.innerHTML = tpl.execute(
                Reflect.field(response.Entry, i)
            );
            list.appendChild(item);
        }
        var cnt = js.Lib.document.getElementById('content');
        cnt.innerHTML = ""; // clear all
        cnt.appendChild(list);
    }

haxe.Template は haxe の標準ライブラリです。
response は Dynamic 型なので field へのアクセスに Reflect を使いました。Dynamic 型、よく分かりません。もっといい方法がありそうです。

では実行してみましょう。
うまく動作していますか?

という事で、一段落。

「普通に JavaScript 書いてる方が幸せじゃーん」という声が聞こえてきそうですが、今後も諦めずに haxe を追いかけて行きたいと思います。
個人的には haXe になかなかの手応えを覚えています。

それではまた近いうちに。

コメント / トラックバックはありません

コメントする