JDK 8u451では、JavaFXはJava SE 8の一部として含まれなくなりました。 詳細は、https://www.oracle.com/javase/javafxを参照してください。
このトピックでは、JavaおよびJavaFXアプリケーションにJavaScriptコードからアクセスする方法、およびJavaScriptコードにJavaおよびJavaFXアプリケーションからアクセスする方法を示します。
アプリケーションは、JavaScriptエンジンを使用して、アプリケーションが埋め込まれているWebページと通信できます。 ホストWebページも、JavaScriptを使用して、埋め込まれたアプリケーションと通信できます。
|
ノート: かなりの範囲まで、この機能は、Java Plug-inで実装されるJavaからJavaScriptへの通信ブリッジに基づいています。 そのため、Javaアプレットで使用可能なドキュメントおよび例の多くは、JavaFXアプリケーションにも適用可能です。 Java実装の詳細は、Java LiveConnectドキュメントを参照してください。 |
この節の内容は以下のとおりです。
JavaScriptからJavaまたはJavaFXアプリケーションにアクセスするための最初のステップは、アプリケーションを表すJavaScriptオブジェクトへの参照を取得することです。 参照を取得する最も容易な方法は、例16-1に示すように、appletタグのname属性を使用して、標準JavaScript getElementById()関数を使用することです。 <fx:deploy> antタスクはappletタグを自動的に生成し、名前は<fx:application>のid属性から取得されます。
結果はアプリケーションのメイン・クラスに対応します。
JavaScriptオブジェクトへの参照を取得することによって、対応するJavaScriptオブジェクトのフィールドとしてJavaオブジェクトを参照して、Javaオブジェクトのパブリック・メソッドおよびフィールドにアクセスするためにJavaScriptコードを使用できます。 app参照を指定した後で、次のコード文のようなことを実行できます。
var r = app.doSomething()
JavaコードでのdoSomething()メソッドの実装は、Javaオブジェクトを返します。 変数rはJavaオブジェクトへの参照になります。 r.doSomethingElse()やapp.dosomethingWithR(r)などのコードを使用できます。 たとえば、例16-2にはJavaコードが含まれ、例16-3にはそのコードと対話するJavaScriptが含まれています。 これらが一緒に動作する方法を確認するには、両方の例を参照してください。
例16-2 Javaコードの例
package testapp;
public class MapApp extends Application {
public int ZOOM_STREET = 10;
public class City {
public City(String name) {...}
...
}
public int currentZipCode;
public void navigateTo(City location, int zoomLevel) {...}
....
public City getCity(String cityName) {...}
....
}
例16-3のJavaScriptスニペットは、例16-2のJavaコードにいくつかの値を渡します。 これらの値がJavaコードで使用される前に、最も近いJavaタイプに自動的に変換されます。
例16-3 例16-2のJavaScriptコード
function navigateTo(cityName) {
//Assumes that the applet tag uses "myMapApp" as the name for this application
var mapApp = document.getElementById("myMapApp");
if (mapApp != null) {
var city = mapApp.getCity(cityName);
mapApp.navigateTo(city, mapApp.ZOOM_STREET);
return mapApp.currentZipCode;
}
return "unknown";
}
window.alert("Area zip: " + navigateTo("San Francisco"));
JavaScriptの文字列、数値およびブール・オブジェクトは、ほとんどのプリミティブJava型(Boolean、byte、char、short、int、long、floatおよびdouble)とjava.lang.Stringに変換できます。
Javaオブジェクトを表すJavaScriptオブジェクト(つまり、以前にJavaから返されたオブジェクト)では、変換すると、そのJavaオブジェクトへの参照が抽出されます。
1つおよび多次元の配列への変換は、個々のオブジェクトの変換のルールに類似したルールに従って、サポートされます。 変換を正常に実行できない場合、JavaScriptエンジンは例外を発生させます。
Webブラウザに返されたすべてのJavaオブジェクトは、特定のアプリケーション・インスタンスに関連付けられています。 JavaScriptエンジンによって保持されるJavaオブジェクトへの参照は、永続的な参照として機能し、ホストしているJVMでそのJavaオブジェクトがガベージ・コレクトされないようにします。 ただし、たとえば、アプリケーションをホストしているWebページから離れたり、HTML DOMツリーからアプリケーションを分離したりすることによって、特定のアプリケーションが破棄された場合、参照はすぐに無効になり、これらのオブジェクトをJavaScriptでさらに使用しようとすると、例外が発生します。
データ型の変換およびオブジェクトの存在期間の詳細は、次を参照してください。
https://www.oracle.com/java/technologies/javase/liveconnect-docs.html#JS_JAVA_CONVERSIONS
|
ノート: Javaオブジェクトがオーバーロード・メソッド(同名だが引数型のセットが異なる複数のメソッド)を持っている場合、最も近い型のメソッドが使用されます。 詳細は、Java LiveConnectドキュメントを参照してください。一般的には、JavaScriptコードから使用する場合は、オーバーロード・メソッドを回避することをお薦めします。 |
アプレットとホストWebページ間の通信の詳細は、JavaチュートリアルのアプレットからのJavaScriptコードの呼出しに関するトピックを参照してください。
JavaFXアプリケーションは次のJavaScriptコンポーネントを呼び出すことができます。
関数
JavaScriptオブジェクトのget、setおよびremoveフィールド
JavaScript配列のgetおよびset要素
JavaFXアプリケーションはJavaScriptコードを評価することもできます。 JavaScript DOM APIを使用して、HTML要素を追加、削除および移動することによって、JavaFXアプリケーションはWebページを動的に変更できます。
JavaFXからJavaScriptへの通信をブートストラップするには、JavaFXアプリケーションは、アプリケーションを含むJavaScriptウィンドウ・オブジェクトへの参照を取得する必要があります。 この参照は、評価、関数の呼出し、および変数のフェッチなど、その後の操作に使用できます。
例16-4に示すように、JavaFX APIのHostServicesクラスにアクセスし、getWebContext()を要求することによって、メイン・アプリケーションとプリローダー・アプリケーションの両方がこの参照を取得できます。
例16-4 JavaFXコードからのHostServicesクラスへのアクセス
public class MyApp extends Application {
private void communicateToHostPage() {
JSObject jsWin = getHostServices().getWebContext();
//null for non-embedded applications
if (jsWin != null) {
//use js
...
}
}
...
}
DOMウィンドウへの参照を含むJavaScriptオブジェクトのすべてのインスタンスが、netscape.javascript.JSObject.のインスタンスとしてJavaコード内に表示されます。
例16-5は、JavaScriptを使用して、実行時にid='myMapApp'で埋め込まれたアプリケーションをサイズ変更する関数を実装する方法を示しています。
例16-5 JavaScriptを使用したブラウザ内のアプリケーションのサイズ変更
public void resizeMyself(int w, int h) {
JSObject jsWin = getHostServices().getWebContext();
if (jsWin != null) {
jsWin.eval("var m = document.getElementById('myMapApp');" +
"m.width=" + w + "; m.height=" + h + ";");
} else {
// running as non embedded => use Stage's setWidth()/setHeight()
}
}
Webページに埋め込まれたJavaFXアプリケーションは、プリローダーまたはメイン・アプリケーション・クラスのinit()メソッドが呼び出された後で、WebページのJavaScriptメソッドを呼び出すことができます。
JavaScriptコードはJavaアプリケーションにいつでもアクセスできますが、アプリケーションがまだ準備できていない場合、アプリケーションの準備が整うまで要求はブロックされます。 特にJavaFXアプリケーションでは、メイン・アプリケーション・クラスのinit()メソッドがまだ終了しておらず、メイン・アプリケーションが自身でWebページへの呼出しを実行していなかった場合に、これが発生します。 プリローダーからのJavaScript呼出しは、JavaScriptからJavaへの通信を完全にブロック解除していません。
ほとんどのブラウザでは、シングル・スレッドのJavaScriptエンジンを使用します。 ブロックが発生すると、ホストWebページおよびブラウザはフリーズしているように見えます。
ホストWebページからアプリケーションに早くアクセスしてブロックを回避するには、アプリケーションからJava関数を呼び出して、アプリケーションの準備ができたときにWebページに通知するか、AntタスクでonJavascriptReadyコールバックを使用します。
例16-6は、onJavascriptReadyコールバックを使用してブラウザをブロックせずにメイン・アプリケーションのdoSomething()メソッドを呼び出すAntタスクのHTMLテンプレートを示しています。
例16-6 AntタスクのHTML入力テンプレート
<html>
<head>
<!-- template: code to load DT JavaScript will be inserted here -->
#DT.SCRIPT.CODE#
<!-- template: code to insert application on page load will be
inserted here -->
#DT.EMBED.CODE.ONLOAD#
<script>
function earlyCallFunction(id) {
//it is safe to call application now
var a = document.getElementById(id);
if (a != null) a.doSomething();
}
</script>
</head>
<body>
<!-- application is inserted here -->
<div id="ZZZ"></div>
</body>
</html>
例16-7は、例16-6のテンプレートからHTMLページを生成するために使用されるAntタスクの該当部分を示しています。 この例では、テンプレートにパスsrc/web/test_template.htmlが含まれていることを前提としています。
JavaScriptから呼び出されるJavaコードは、JavaFXアプリケーション・スレッドではない特別なスレッドで実行されます。 JavaFXコードのPlatform.runLater()メソッドを使用して、JavaFXアプリケーション・スレッドで何か実行されていることを確認します。
一般に、JavaScriptから呼び出される関数からできるだけ早く返します。 ほとんどの最新のブラウザでは、JavaScriptエンジンはシングル・スレッドです。 呼出しが進まないと、Webページはフリーズしているように見え、ブラウザは応答がありません。 特に、JavaFXアプリケーション・スレッドで行われる作業を待機するコードを書き込まないようにします。 JavaScriptコードがこの作業の結果に依存している場合、Javaからコールバックを使用して、その作業の実行の結果のJavaScriptコードを通知します。
例16-8は、JavaScriptで回避するコードの例を示しています。
例16-8 JavaScriptスレッドをブロックするネイティブ実装
function process(r) {
window.alert("Result: "+r);
}
var result = myApp.doSomethingLong();
process(result);
例16-9は、JavaScriptコードで従ったほうがよいパターンを示しています。
例16-9 例16-8のより適切な実装
function process(r) {
window.alert("Result: "+r);
}
myApp.doSomethingLong(function(r) {process(r);});
例16-10は、Javaコードにおける適切な例を示しています。
例16-10 コールバックを使用したJavaコード
public void doSomethingLong(JSObject callback) {
Object result;
//do whatever is needed to get result
//Invoke callback
// callback is a function object, and every function object
// has a "call" method
Object f[] = new Object[2];
f[0] = null; //first element is object instance but this is global function
//not applying it to any specific object
f[1] = new String(result); //real argument
callback.call("call", f);
}
Javaコードは、JavaFXアプリケーション・スレッドを含むどのスレッドからもJavaScriptを呼び出すことができます。 ただし、ブラウザのJavaScriptエンジンがビジー状態の場合、JavaScriptへの呼出しは、しばらくの間進まない可能性があります。 JavaFXアプリケーション・スレッドでの呼出しがある場合、画面の更新やユーザー・イベントの処理ができないため、アプリケーションがフリーズしたように見えることがあります。 この状況を回避するには、JavaFXアプリケーション・スレッドからのLiveConnect呼出しの実行の負荷を軽減します。
Webページ上のJavaScriptコードは、そのページ上のアプリケーションへの呼出しを常に行うことができます。 JavaScriptコードは、アプリケーションでロードされるJavaクラスのすべてのパブリック・メソッドおよびフィールドにアクセスすることもできます。 ただし、JavaScriptからJavaへの呼出しが行われると、その呼出しはサンドボックス環境からの呼出しとして扱われます。 また、HTMLドキュメントとアプリケーションが異なるサイトを元にしている場合、Webページ上のJavaScriptは、それのためにネットワーク接続を行うことはできません。
この制限を除いて、アプリケーションがサンドボックスで実行される場合、JavaScriptからのJavaの呼出しは他に影響を及ぼしません。 ただし、アプリケーションが高い権限を要求した場合、JavaScriptからのJavaメソッドへの呼出しは高い権限なしでサンドボックスで実行され、セキュリティ警告が発行されます。 高い権限が必要な場合、Java APIのAccessController.doPrivilegedを使用して、信頼されたコードで権限を要求できます。
信頼されていないJavaScriptコードで誤って追加の権限を付与したアプリケーションでAPIを公開しないように注意してください。 JavaScriptコードに高い権限を付与する必要がある場合、検証可能なHTTPS接続でアプリケーションを提供し、アプリケーションをホストしているWebページのドキュメント・ベースがアプリケーションのコードの目的とする提供元と同じであることを確認するためのチェックを実行します。
この項には、JavaFXとJavaScript間の通信を使用してJavaFX Webアプリケーションをブラウザと統合する方法を示すサンプルが含まれています。 例16-11は、Webページに20個のタブを持つタブ・ペインを作成するJavaFXアプリケーションを示しています。
例16-11 埋込みWebページでのタブの作成
public class TabbedApp extends Application {
Group root = new Group();
TabPane tabPane = new TabPane();
public void init() {
// Prepare tab pane with set of tabs
BorderPane borderPane = new BorderPane();
tabPane.setPrefSize(400, 400);
tabPane.setSide(Side.TOP);
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
for(int i=1; i<=20; i++) {
final Tab t = new Tab("T" + i);
t.setId(""+i);
Text text = new Text("Tab "+i);
text.setFont(new Font(100));
BorderPane p = new BorderPane();
p.setCenter(text);
t.setContent(p);
tabPane.getTabs().add(t);
}
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
アクセスしたタブの履歴をブラウザ履歴に保存するように、このアプリケーションをさらに改善できます。 これにより、ユーザーは、ブラウザの「戻る」および「進む」ボタンをクリックすると、タブ間を移動できます。
実装は、HTML 5で導入されたonhashchangeイベントに基づいており、次の場所で説明されています。
http://www.whatwg.org/specs/web-apps/current-work/#event-hashchange
同様の効果を実現するためにAJAXアプリケーションで使用されるJavaScript技術は、ドキュメントURLのハッシュ部分に現在の選択への参照を保存することです。 ユーザーが「戻る」ボタンをクリックすると、URLが更新され、復元する必要がある選択状態を抽出できます。
この解決策を実装するために、onNavigate()とnavigateTo()の2つの新しいメソッドがサンプルに追加されます。 新しいタブが選択されるたびに、onNavigate()メソッドが呼び出されます。 JavaScriptメソッドnavigateTo()を呼び出し、それにタブIDを渡すことによって、このメソッドは新しい選択に関する情報をWebページに配信します。 JavaScriptコードはタブIDをURLハッシュに保存します。
navigateTo()メソッドは逆の同期の役割を果たします。 WebページURLが変更されると、選択されるタブのIDを使用してこのメソッドが呼び出されます。
例16-12は、アプリケーションの更新されたコードを示しています。 例16-11と異なるコードは太字で示しています。
例16-12 タブ履歴を保存する改善されたアプリケーション
public class TabbedApp extends Application {
Group root = new Group();
TabPane tabPane = new TabPane();
public void init() {
// Prepare tab pane with set of tabs
BorderPane borderPane = new BorderPane();
tabPane.setPrefSize(400, 400);
tabPane.setSide(Side.TOP);
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
for(int i=1; i<=20; i++) {
final Tab t = new Tab("T" + i);
t.setId(""+i);
Text text = new Text("Tab "+i);
text.setFont(new Font(100));
BorderPane p = new BorderPane();
p.setCenter(text);
t.setContent(p);
// When tab is selected, notify web page to save this in the
// browser history
t.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov,
Boolean tOld, Boolean tNew) {
if (Boolean.TRUE.equals((tNew))) {
onNavigate(t.getId());
}
}
});
tabPane.getTabs().add(t);
}
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public void navigateTo(String tab) {
for (Tab t: tabPane.getTabs()) {
if (tab.equals("#"+t.getId())) {
tabPane.getSelectionModel().select(t);
return;
}
}
}
private void onNavigate(String tab) {
JSObject jsWin = getHostServices().getWebContext();
// Null for nonembedded applications
if (jsWin != null) {
//use js
jsWin.eval("navigateTo('" + tab + "')");
}
}
}
実装ロジックの部分はHTMLページ内にあります。 例16-13は、Antスクリプトの入力テンプレートとして使用されるページを示しています。 Antスクリプトが実行されると、JavaFXアプリケーションを埋め込むコードを、カスタムJavaScriptコードの隣に挿入します。 入力テンプレートの詳細は、「<fx:template>」を参照してください。
JavaScript関数の実装は簡単です。 <body>タグのonhashchange属性は、URLのハッシュ部分の更新の通知に署名するために使用されます。 イベントが取得されると、JavaFXアプリケーションがWebページに埋め込まれ、navigateTo()メソッドが呼び出されます。
アプリケーションが選択されたタブの更新で呼び出す場合、URLのハッシュ部分に保存されます。
例16-13 Antスクリプトへの入力として使用されるHTMLテンプレート
<html>
<head>
<!-- template: code to load DT javascript will be inserted here -->
#DT.SCRIPT.CODE#
<!-- template: code to insert application on page load will be
inserted here -->
#DT.EMBED.CODE.ONLOAD#
<script>
function hashchanged(event) {
var a = document.getElementById('tabbedApp');
if (a != null) {
try {
a.navigateTo(location.hash);
} catch (err) {
alert("JS Exception: " + err);
}
}
}
function navigateTo(newtab) {
if (window.location.hash != newtab) {
window.location.hash = newtab;
}
}
</script>
</head>
<body onhashchange="hashchanged(event)">
<h2>Test page</h2>
<!-- Application will be inserted here -->
<div id='javafx-app-placeholder'></div>
</body>
</html>
完全性を期すために、例16-14は、このサンプルのデプロイに使用されるAntスクリプトを示しています。 アプリケーションは、ID tabbedAppで作成されます。 JavaScriptコードはこのIDを使用して、ページ上のアプリケーションを検索し、HTMLテンプレートはこれを使用して、Antタスクによって生成されたカスタムHTMLページにアプリケーションを埋め込みます。
例16-14 アプリケーションをパッケージ化するAntスクリプト
<fx:application id="tabbedApp"
name="Example of browser integration"
mainClass="docsamples.TabbedApp"/>
<fx:jar destfile="dist/docsamples/tabbedapp.jar">
<fx:application refid="tabbedApp"/>
<fileset refid="appclasses"/>
</fx:jar>
<fx:deploy width="400" height="400"
outdir="dist-web"
outfile="BrowserIntegrationApp">
<fx:info title="Doc sample"/>
<fx:application refid="tabbedApp"/>
<fx:template
file="src/template/TabbedApp_template.html"
tofile="dist-web/TabbedApp.html"/>
<fx:resources>
<fx:fileset requiredFor="startup" dir="dist/docsamples">
<include name="tabbedapp.jar"/>
</fx:fileset>
</fx:resources>
</fx:deploy>