Titaniumのネイティブ側とWebViewのHTML内のJavascriptでメソッドの実行やデータを橋渡しする仕組みとしては、evalJS()とTi.App.fireEvent/Ti.App.addEventListenerの二通りがあります。
iOSではもともとevalJSにあたる機能は用意されていますがWebViewからネイティブ側を呼び出せるようにはなっていません。ではどうやって実装されているのでしょうか?。
WebViewでHTMLをロードした際にTitaniumは こちらのJavascriptを追加します。
このうち、HTML側で呼び出されたTi.App.xxxやTi.API.xxxは
Ti.App.fireEvent = function (name, evt) {
Ti._broker('App', 'fireEvent', {
name: name,
event: evt
})
};
のようにTi._broker(module, method, data)を呼び出します。
次にTi._broker()では「app://〜」で始まるURLにidやデータをシリアライズしたパラメータを追加したXMLHttpRequestを発行します。
Ti.App._xhr = XMLHttpRequest;
Ti._broker = function (module, method, data) {
try {
var url = 'app://' + Ti.appId + '/_TiA0_' + Ti.pageToken + '/' + module + '/' + method + '?' + Ti.App._JSON(data, 1);
var xhr = new Ti.App._xhr();
xhr.open('GET', url, false);
xhr.send()
} catch (X) {}
};
「え、もしかしてTitaniumは内部にサーバーを起動させていて、、、?」いやそんなことはなく、単にネイティブ側はWebViewから呼び出されるURLの宛先を監視していて、そのうちapp://〜についてパラメータを解析することで実行するTitanium側のメソッドや引数を決定しているようです。
逆にネイティブ側からHTML側のTi.App.addEventListener (name, callback)でイベントを受け取る仕組みは、
Ti.App._listeners = {};
Ti.App._listener_id = 1;
//(略)
Ti.App.addEventListener = function (name, fn) {
var listeners = Ti.App._listeners[name];
if (typeof (listeners) == 'undefined') {
listeners = [];
Ti.App._listeners[name] = listeners
}
var newid = Ti.pageToken + Ti.App._listener_id++;
listeners.push({
callback: fn,
id: newid
});
Ti._broker('App', 'addEventListener', {
name: name,
id: newid
})
};
で受け取るイベントをlisteners配列を登録しておきます。
そしてネイティブ側は登録されたlistenersに対するイベントがfireされるとTi.App._dispatchEvent(type, evtid, evt)を呼び出します。
Ti.App._dispatchEvent = function (type, evtid, evt) {
var listeners = Ti.App._listeners[type];
if (listeners) {
for (var c = 0; c < listeners.length; c++) {
var entry = listeners[c];
if (entry.id == evtid) {
entry.callback.call(entry.callback, evt)
}
}
}
};
こうしてネイティブとHTML内でメソッドやデータを受け渡しが他のグローバルイベントと同じ書き方で手軽に利用できる点はTitaniumのCoolな部分ではないでしょうか。