JavaScriptからActionScriptを呼び出す

## JavaScriptからActionScriptを呼び出す

実際にはコールバックするのでJS→AS→JSという流れになります。

想定は以下のとおり

  * 回答フォームの一部がFlashで作られている
  * 送信ボタン（という名のinput[type="button"]）を押したときにASでエラーチェック処理
  * AS側での処理が終わったらJS（jQuery）に処理を返す

### そもそも

なんでこんなことをしたいと思ったのかという理由は以下の通り

 * ラジオボタンやチェックボックスといったHTMLのフォーム部品を使った設問とFlashの設問を同一ページ上でまとめて送信したい
 * FlashでPOSTでデータを送出してページ遷移してから、ページを戻ってくると（戻るボタンではない）、ポストの値が不正値（弊社フレームワーク的に）を示してアンケートが続けられなくなってしまう。（現状、次のページをhiddenタグだけの空白ページにしてフォームを強制送信することで対応。結果、戻ることはできない。どうやらフォームから送信すればこの事象は起きない。）
 * その他、弊社フレームワーク的に工数的に都合の良いことができるので
 * Flasherとしてお仕事したいので、間口を広げるために
 * 上記を満たしつつ、作っていて楽しそうだから

IEがやってくれましたが対応は完了しました。泣けます。
IE7～11に対応しました。

### Flashの記述

Flashの記述は簡単なものです。
テストなので必ずfalseを返す関数を用意しています。
実際の運用では、ここにエラーチェックの処理を書いて、エラーがあればエラーコードを返し、エラーがなければユーザが設定した値を返すという形になります。

```as3:Main.as
package {
	import flash.display.MovieClip;
	import flash.external.ExternalInterface;
	public class Main extends MovieClip {
		public function Main() {
			// constructor code
			ExternalInterface.addCallback("flash_function", test);
		}
		private function test():Boolean {
			return false;
		}
	}	
}
```

ExternalInterface.addCallbackの引数にはJSから指定する関数名とそれに結びつく内部の関数名を指定します。

### objectタグの記述

HTMLはFlashのIDEが吐き出したソースを元に一部手を加えています。
Flashが入っていないときに表示されるインストールを促すバナーの記述はとってあります。

```html:index.html
<form>
<div id="q1flashContent">
	<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="q1external" width="440" height="523" align="middle">
		<param name="movie" value="img/q1.swf" />
		<param name="quality" value="high" />
		<param name="bgcolor" value="#ffffff" />
		<param name="play" value="true" />
		<param name="loop" value="true" />
		<param name="wmode" value="window" />
		<param name="scale" value="showall" />
		<param name="menu" value="true" />
		<param name="devicefont" value="false" />
		<param name="salign" value="" />
		<param name="allowScriptAccess" value="always" />
		<!--[if !IE]>-->
		<object type="application/x-shockwave-flash" data="img/q1.swf" width="440" height="523">
			<param name="movie" value="img/q1.swf" />
			<param name="quality" value="high" />
			<param name="bgcolor" value="#ffffff" />
			<param name="play" value="true" />
			<param name="loop" value="true" />
			<param name="wmode" value="window" />
			<param name="scale" value="showall" />
			<param name="menu" value="true" />
			<param name="devicefont" value="false" />
			<param name="salign" value="" />
			<param name="allowScriptAccess" value="always" />
		</object>
		<!--<![endif]-->
	</object>
</div>
<div>
	<input type="button" name="next" value="送信"></div>
</form>
```

このタグの書き方にたくさんの注意点があります。

 * 外側のobjectのidには「external」という文字列が含まれなくてはいけない
 * paramのallowScriptAccessはalwaysにしなくてはいけない
 * if !IEというコメントタグはIE10以上は無視をする
 * IE11は外側のobjectではなく、内側のobjectを見ている（FirefoxやChromeと同じ）

試しにif !IEと囲まれた部分を削ってIE11で見てみてください。SWFが表示されなくなります。

### jQueryの記述

jQueryは1.8.3を想定しています。古いバージョンだとon()とか使えないので注意です。

はじめは「objectタグがif !IEで出し分けられているのだからscriptタグも同じように出し分ければ余裕！」と思っていました。

しかし、以下の点からそれはできないことがわかりました。

 * IE10以降はif !IEのコメントタグを単なるコメントとしてしか見ない
 * IE11はFirefoxやChromeと同じタグを見る

そのため、IEかどうかとIEならそのバージョンをとる必要がでてきました。そこで出てきたのが「documentMode」です。どのバージョンのIEの表示モードでブラウズされているかをuintで返します。もちろんIE以外ではundefinedです。

```js:index.html
jQuery(function($){
	// Flashのobjectを補足
	var flashObj = $('#q1flashContent object').get(0);
	if(!this.documentMode || this.documentMode==11){
		flashObj = $('#q1flashContent object').get(1);
	}
	
	// テスト用確認
	console.log(flashObj);
	console.log("this.documentMode:" + this.documentMode);
	
	// 送信時メソッド設定
	$('input[name="next"]').on("click", function(){
		// ASに投げてコールバックをアラート表示
		alert(flashObj.flash_function());
	});
});
```

セレクターで拾ってくるとjQueryオブジェクトが返ってきてしまうので、それをgetで普通の配列に戻して、更に添字を指定することで、getElementByIdとかのネイティブで指定したのと同じオブジェクトが返ってくるようにしています。

IEはF12で開発者ツールが開き、どのdocumentModeを使うかシミュレートできるので、切り替えて確認することができます。

「テスト用確認」で書かれているコードにより、コンソールで確認することができます。

### 余談

Flashの中でエラー表示をしてしまうと、その表示の解除をユーザにやらせることになるのはスマートじゃないかなと思っています。

エラーをJQueryで受け止めて表示をしてもいいけれど、swfにオーバーレイするのはちょっと表示で罠が発生しそうなので、フレームワークにのっとってサーバサイドでエラー処理をするようにしたいなと思っています。

そうするとエラー時にFlashを送信時の状態に再現する必要がでてくるので（Flash以外の場所で入力エラーを表示したときにFlashでの入力がリセットされるのはツライ）、Externalな話はもう少し続きそうです。