イントロダクション
Cordova Pluginを実装する場合のJavaScript部分の記述方法について、まとめました。
Cordovaプラグインについての記事
- 基本事項
- JavaScript部分の実装 <- この記事
- [Native部分の実装iOS] ... TODO
- [Native部分の実装Android] ... TODO
- フックスクリプト
- [プラグインのテスト] ... TODO
plugin.xmlへの記述法
Pluginとして組み込みたいjsファイルを指定して、 js-module
タグで記述します。
例えば、cordova-plugin-camera
であれば次のようになっています。
<js-module src="www/CameraConstants.js" name="Camera">
<clobbers target="Camera" />
</js-module>
<js-module src="www/CameraPopoverOptions.js" name="CameraPopoverOptions">
<clobbers target="CameraPopoverOptions" />
</js-module>
<js-module src="www/Camera.js" name="camera">
<clobbers target="navigator.camera" />
</js-module>
ここでは、3つのjsファイルを組み込んでいます。このように、一つのプラグインで複数のjsファイルを読み込むことも出来ますが、通常は、組み込むのjsファイルは1つだけあれば十分です。
-
src属性で、組み込むJSファイルへのパスを記述します。
-
name属性は、そのjs-moduleのIDとして利用されます。ただし、アプリの開発者がjs-moduleのIDを直接参照することはないので、特に意識する必要はありません。js-moduleのIDは、
「プラグインのID」.「name属性で指定した文字列」
となります。例えば、上記のカメラプラグインの例であれば、3つのjs-moduleについて、IDはそれぞれ -
cordova-plugin-camera.Camera
-
cordova-plugin-camera.CameraPopoverOptions
-
cordova-plugin-camera.camera
となります。prepare後に生成される www/cordova-plugins.js
の中で、上記が定義され、cordova.jsから読み込まれます。
- js-module内で設定しているclobbersは重要です。srcで読み込まれたJSモジュール(後述)は、このclobbersのtargetで設定したオブジェクトとして参照出来るようになります。これについては、次節で詳しく説明します。
js-moduleとclobbersの関係
js-moduleのsrcタグで指定するモジュール(jsファイル)は、Common-JS形式のモジュールと似ていて、次のような形式になります。
module.exports = [オブジェクト]
例えば、上記のカメラプラグインの3番目のjs-moduleを見てみると、次のような形式になっています。
var cameraExport = {};
cameraExport.getPicture = function (successCallback, errorCallback, options) {
...
}
module.exports = cameraExport;
そして、module.exportsしたオブジェクトは、clobbersのtargetで設定したオブジェクトとして参照出来るようになるため、このサンプルでは、navigator.camera
が、cameraExport
となり、結局、次のように呼び出せます。
navigator.camera.getPicture( ... );
clobber以外のオプション
js-moduleタグの中に記述するのは、clobber以外に、mergesとrunsがあります。
merges
clobbersとほぼ同様で、次のようにtarget
属性と共に記述して使います。
<js-module src="www/CameraConstants.js" name="Camera">
<merges target="window.cordova" />
</js-module>
この例では、window.cordovaオブジェクトに組み込まれて参照出来るようになります。clobbersとの違いは、すでに存在しているwindow.cordovaオブジェクトに、このモジュールで定義しているgetPicture
などのプロパティを追加するところです。
runs
属性を指定せずに次のように記述します。
<js-module src="www/CameraConstants.js" name="Camera">
<runs />
</js-module>
runsを指定した場合、モジュールのプロパティを参照するためのオブジェクトが用意されません。cordova.requireを使って、自前で参照することになります。
モジュールの書き方
ネイティブコードの呼び出し
ネイティブコードの呼び出しは、cordova.exec
を使います。次のような形式です。
cordova.exec(success, failure, '【プラグイン(のフィーチャー)名】', '【メソッド名】', [【引数】]);
プラグイン(のフィーチャー)名は、plugin.xmlのconfig-file
タグの中のfeature
タグのname
属性でつけた値です。例えば、Androidで次のようになっていたとすると、
<config-file target="res/xml/config.xml" parent="/*">
<feature name="HelloWorldPlugin">
<param name="android-package" value="com.example.plugin.HelloWorldPlugin"/>
</feature>
</config-file>
HelloWorldPlugin
というのが、プラグイン(のフィーチャー)名になります。
メソッド名は、呼び出すメソッド名です。Androidの場合、ネイティブ側のプラグインクラスのexecute
メソッドの第一引数であるaction
変数に、メソッド名が引き渡されます。コードで書くと、次のようになります。
public class HelloWorldPlugin extends CordovaPlugin {
...
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
// このactionに、メソッド名が格納される。
// argsに、引数が格納される。
...
}
}
iOSの場合は、Androidと同様、plugin.xmlのconfig-file
タグの中のfeature
タグのname
属性でつけた値です。例えば、
<config-file target="config.xml" parent="/*">
<feature name="HelloWorldPlugin">
<param name="ios-package" value="CDVHelloWorldPlugin"/>
</feature>
</config-file>
HelloWorldPlugin
というのが、プラグイン(のフィーチャー)名になります。
メソッド名は、iOSの場合、ネイティブ側のメソッドそのものになります。上記の例では、CDVHelloWorldPluginクラスのメソッドが直接呼ばれるので、例えば
cordova.exec(success, failure, '【プラグイン(のフィーチャー)名】', 'myfunc', [【引数】]);
で呼び出すと、
@implementation CDVHelloWorldPlugin
...
- (void) myfunc:(CDVInvokedUrlCommand*) command
{
// command.argumentsに、引数が格納される
...
}
として定義されたmyfuncメソッドが直接呼ばれます。このとき、myfuncメソッド自体の引数はかならず、CDVInvokedUrlCommand *
になります。jsから渡された引数は、command.arguments
で取得します。
argsCheck
TODO
channelとpub/sub
channelを使うことで、pub/subを実現することが出来ます。例えば、cordovaが利用可能になった時に何か実行させたい場合、次のように実装します。
var channel = require('cordova/channel');
channel.onCordovaReady.subscribe(function() {
// 起動時の処理
});
onCordovaReady
イベントが発火した時に実行する処理を、subscribe
メソッドで定義しています。
このように定義すると、onCordovaReady
イベントが発火したときに、定義した処理が自動的に実行されます。
後述するように、onCordovaReadyイベントはスティッキーイベントであるため、subscribe
を定義する以前にonCordovaReadyイベントがすでに発火済みだった場合、subscribe
するとすぐにその処理が実行されます。
channelで定義されているイベントには、他にも次のようなものがあります。
- onDOMContentLoaded
- onNativeReady
- onCordovaReady
- onPluginsReady
- onDeviceReady
- onResume
- onPause
onNativeReady
, onPluginsReady
が発火した後、onCordovaReady
がイベントが発火します。
また、onDOMContentLoaded
、onCordovaReady
が共に発火すると、onDeviceReady
イベントが発火します。(他にも、waitForInitializationで登録されたイベントがあると、それらすべてが発火後に、
onDeviceReady`イベントが発火します)
少し紛らわしいですが、'onCordovaReadyイベントと、
onDeviceReady`イベントは別のイベントであることに注意してください。
なお、onDOMContentLoaded
イベントと、最後にReady
が付いているイベントは、スティッキーイベントであり、一度そのイベントが発火すると、以後は発火したままの状態となります。(発火されたままのイベントに対しては、イベント発火後にsubscribeするとただちに実行されます。スティッキーでないイベントは、発火後にsubscribeしても実行されません)
詳しくは、https://github.com/apache/cordova-js/blob/c75e8059114255d1dbae1ede398e6626708ee9f3/src/common/channel.js を参照してください。
channelイベントを自前で作成する
cordovaでデフォルトで定義されている上記のイベント以外に、自前で新規にイベントを作成することも出来ます。
例えば、cordova-plugin-mediaプラグインでは、次のようにonMediaPluginReady
イベントを作成し、利用しています。(このコードは、プラットフォームがandroid, amazon-fireos, windowsphoneときのみ読み込まれますので、iOSでは実行されません)
var channel = require('cordova/channel');
channel.createSticky('onMediaPluginReady');
channel.waitForInitialization('onMediaPluginReady');
channel.onCordovaReady.subscribe(function() {
exec(onMessageFromNative, undefined, 'Media', 'messageChannel', []);
channel.initializationComplete('onMediaPluginReady');
});
createSticky
メソッドで、スティッキーなイベント(一度発火したら、発火したままの状態になる)が作られます。(スティッキーでないイベントを作る場合は、create
メソッドを使います)
waitForInitialization
で、onMediaPluginReadyイベントを、初期化待ちイベントとして追加します。初期化待ちイベントがすべて発火しないと、cordovaのdevicereadyイベントは発火しません。すなわち、このように実装することで、deviceready時にonMediaPluginReadyイベントはすでに発火済みであることを保証することが出来ます。
最後の行は、onCordovaReady
イベントが発火後に、ネイティブコードの呼び出し
exec(onMessageFromNative, undefined, 'Media', 'messageChannel', []);
が行われ(非同期なので、呼び出しのみ。Nativeからの呼び出しを受け付けるonMessageFromNativeメソッドを登録している)、その後、
channel.initializationComplete('onMediaPluginReady');
});
により、onMediaPluginReadyイベントを発火しています。
(ここではこのようにしましたが、これだとexecの処理が完了するonMediaPluginReady
イベントが発火してしまうので、外部にメッセージを送っておくりっぱなし(レスポンスを気にしない)ような処理はいいのですが、execの処理の結果を使ったこともしておきたいのであれば、onMessageFromNative
関数の内部でonMediaPluginReady
イベントを発火してください)
モジュールの組み込まれ方と開発作業
プラットフォームごとに違いますが、基本的な構造は同じなので、iOS (cordova-ios 4.5.4)の場合を例にとって説明します。
プラグインのjs-moduleで設定したコードは、plugin add
もしくはplatform add
を実行した時点で、platforms/ios/platform_www/plugins/プラグイン名/www
の下にコピーされます。このファイルは、cordova prepare
を実行しても変更されないので、開発時にはこのファイルを編集すると良いでしょう。
platforms
┗ ios
┗ platform_www
┗ plugins
┗ プラグイン名
┗ www
┗ モジュールのファイル名.js
なお、このファイルは厳密にはjs-moduleのsrcで指定しているファイルそのものではなく、一行目と最終行にコードが追加され、次のようになっています。
cordova.define("[モジュールのID]", function(require, exports, module) {
// js-moduleのsrcで指定したファイルの中身
});
そのため、開発時にこのコードを編集し、完成後にプラグイン側にフィードバックする場合(js-moduleで設定したファイルとして保存する場合)、一行目と最終行目を除いておく必要があるので注意です。
cordova prepareの実行
cordova prepare
を実行すると、platforms/ios/platform_www/plugins/プラグイン名/www
配下のコードは、platforms/ios/www/plugins/プラグイン名/www
配下にコピーされます。実際にアプリに組み込まれるのはこちらのファイルです。
platforms
┗ ios
┗ www
┗ plugins
┗ プラグイン名
┗ www
┗ モジュールのファイル名.js
こちらは、cordova prepare
するたびに上書きされてしまうので、デバッグ用やテスト用のコードなど、ちょっとだけ確認したいをここに記述しても良いかもしれません。