CDATA SectionでJavaScript APIを使う

先日、twitter上で



http://twitter.com/naoto5959/status/2026978673


とか呟いておりましたら、muta244さんから素敵なライブラリを紹介して頂きました。



http://twitter.com/muta244/status/2027055533



muta244さんのas3ライブラリ集にASからJavaScriptを簡単に操れるクラスが含まれています。とても便利なライブラリなのでみんな使うといいと思います。


unbland as3 library

http://bitbucket.org/muta244/unbland-as3-library/wiki/Home

JavaScriptライブラリのサンプル

http://bitbucket.org/muta244/unbland-as3-library/src/tip/examples/core/external/src/ExampleJavaScript.as



と、紹介しつつ今回そのライブラリの使い方は書かないという。

CDATA Sectionでヒアドキュメントみたいな何か

さて、先のソースの下記部分を読んでいて、驚きました。as3のソース内にJavaScriptがふつうに書いてあるのです。



http://bitbucket.org/muta244/unbland-as3-library/src/tip/src/org/unbland/core/external/JSObject.as#cl-288



知らなかったのですが、as3ではE4Xが使えるのでCDATA Sectionを使えばヒアドキュメントみたいなことが出来るんですね。なんていうか以下みたいな感じです。

var hogeString:String = <![CDATA[
あのイーハトーヴォの
すきとおった風、
夏でも底に冷たさをもつ青いそら、
うつくしい森で飾られたモリーオ市、
郊外のぎらぎらひかる草の波。
]];
trace(hogeString);

これをtraceした結果は以下になります。

あのイーハトーヴォの

すきとおった風、

夏でも底に冷たさをもつ青いそら、

うつくしい森で飾られたモリーオ市、

郊外のぎらぎらひかる草の波。

改行が1つ多くなってしまうのは、改行コードの問題と思われ、以下に示す改行コードの変換処理をソースに追加してみます。

var CR:String = String.fromCharCode(13);
var LF:String = String.fromCharCode(10);
hogeString = hogeString.replace(new RegExp(CR + LF, "gm"), LF); 

これで改行コードをLFに変換出来ました。as3にはヒアドキュメントはないと思っていたのですが、CDATA Sectionを使っていわゆるヒアドキュメントような使い方が出来る事がわかりました。

CDATA SectionとExternalInterfaceでJavaScript

今まで、asとjsの連携というと、swfを読み込んだhtml上にjavascriptを書いておく(もちろん外部読み込みでも良い)ものだと思っていたで以下のような感じで実装してました。

htmlソース
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>demo</title>
<script type="text/javascript" src="path_to/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="path_to/swfobject.js"></script>
<script type="text/javascript">
swfobject.embedSWF(
  "demo.swf", "swf_container", "550", "400", "9.0.0",
  undefined,
  {}, // flashvars
  {
    quality : 'high',
    allowScriptAccess : 'always'
  }, // params
  {
    id : "externalswf",
    name : "externalswf"
  } // attributes
);
function getExternalAPI() {
  $.ajax({
    url: 'path_to_external_API',
    dataType: 'text',
    cache: false,
    success: function(data, status) {
      $('#externalswf').get(0).getExternalAPI_Result(data);
    },
    error: function(xhr, status, e) {
    }
  });
}

</script>
</head>
<body>
<div id="swf_container">FLASH content</div>
</body>
</html>
demo.swfのDocumentClassに指定したASソース
package
{
  import flash.display.Sprite;
  import flash.external.ExternalInterface;
  import flash.system.Security;
  public class Main extends Sprite
  {
    //----------------------------------------------------------------------
    //  method
    //----------------------------------------------------------------------
    //------------------------------
    //  public method
    //------------------------------
    /**
     * constructor
     */
    public function Main()
    {
      // ExternalInterfaceの使用可否を確認
      if (!ExternalInterface.available) throw Error("ExternalInterface is unavailable");
      
      // swfを読み込んだhtmlが置いてあるドメインのjs -> asアクセスを許可する
      Security.allowDomain(ExternalInterface.call("function() { return location.hostname }"));
      
      // js からgetExternalAPI_Resultの名前でrenderExternalAPIResultメソッドを呼び出せるようにする
      ExternalInterface.addCallback("getExternalAPI_Result", renderExternalAPIResult);
      
      // jsのgetExternalAPIを実行
      ExternalInterface.call("getExternalAPI");
    }
    
    //------------------------------
    //  private method
    //------------------------------
    /**
     * render external API result via javascript
     */
    private function renderExternalAPIResult(data:*):void
    {
      // dataを使って何かする
    }
  }
}

このようなソースを先ほどのCDATA Sectionを使って、htmlソースをちょっとすっきりさせてみました。

すっきりした(そうでもない?)htmlソース
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>demo</title>
<script type="text/javascript" src="path_to/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="path_to/swfobject.js"></script>
<script type="text/javascript">
swfobject.embedSWF(
  "demo.swf", "swf_container", "550", "400", "9.0.0",
  undefined,
  {}, // flashvars
  {
    quality : 'high',
    allowScriptAccess : 'always'
  }, // params
  {
    id : "externalswf",
    name : "externalswf"
  } // attributes
);
</script>
</head>
<body>
<div id="swf_container">FLASH content</div>
</body>
</html>
jsコードを内包したASソース
package
{
  import flash.display.Sprite;
  import flash.external.ExternalInterface;
  import flash.system.Security;
  public class Main extends Sprite
  {
    //----------------------------------------------------------------------
    //  method
    //----------------------------------------------------------------------
    //------------------------------
    //  public method
    //------------------------------
    /**
     * constructor
     */
    public function Main()
    {
      // ExternalInterfaceの使用可否を確認
      if (!ExternalInterface.available) throw Error("ExternalInterface is unavailable");
      
      // swfを読み込んだhtmlが置いてあるドメインのjs -> asアクセスを許可する
      Security.allowDomain(ExternalInterface.call("function() { return location.hostname }"));
      
      // js からgetExternalAPI_Resultの名前でrenderExternalAPIResultメソッドを呼び出せるようにする
      ExternalInterface.addCallback("getExternalAPI_Result", renderExternalAPIResult);
      
      // jsのgetExternalAPIを実行
      executeJS();
    }
    
    //------------------------------
    //  private method
    //------------------------------
    /**
     * execute JavaScript via ExternalInterface
     * @param  data
     */
    private function executeJS():void
    {
      // 本当は改行とか空行を置換してから実行するほうが素敵
      ExternalInterface.call(<![CDATA[
        function getExternalAPI() {
          $.ajax({
          url: 'path_to_external_API',
          dataType: 'text',
          cache: false,
          success: function(data, status) {
            $('#externalswf').get(0).getExternalAPI_Result(data);
          },
          error: function(xhr, status, e) {
          }
          });
        }
      ]]>);
    }
    
    /**
     * render external API result via javascript
     */
    private function renderExternalAPIResult(data:*):void
    {
      // dataを使って何かする
    }
  }
}

おおざっぱに書くとこんな感じです。as側だけいじればいいってのは楽でいいですね。

opensocialの話(たったの1行!)

as側にjsがかけるので同じように、opensocialJavaScript APIを叩けばOKです。opensocial-jqueryであれば、外部APIを参照する処理とかはこのまま使えそうですね。

ExternalInterface.objectID

ところで、js -> asのメソッドを呼び出す箇所で

$('#externalswf').get(0).getExternalAPI_Result(data);

という箇所があって、「externalswf」がハードコードじゃん、うげ。という感じがします。
この辺は、ExternalInterface.objectIDを使えばすっきりです。ただし、このExternalInterface.objectIDをfirefoxで使う際にはswfobjectでidだけでなくnameにも同じ値を与えてあげることを忘れないようにしましょう。

swfobject.embedSWF(
  "demo.swf", "swf_container", "550", "400", "9.0.0",
  undefined,
  {}, // flashvars
  {
    quality : 'high',
    allowScriptAccess : 'always'
  }, // params
  {
    id : "externalswf",
    name : "externalswf" // ここね
  } // attributes
);

そして、as側はこんな感じですかね。

private function executeJS():void
{
  ExternalInterface.call(<![CDATA[
    function getExternalAPI() {
      var objectId = "]]> + ExternalInterface.objectID + <![CDATA[";
      $.ajax({
      url: 'path_to_external_API',
      dataType: 'text',
      cache: false,
      success: function(data, status) {
        $('#' + objectId).get(0).getExternalAPI_Result(data);
      },
      error: function(xhr, status, e) {
      }
      });
    }
  ]]>);
}

まとめ

  • as3でヒアドキュメントっぽいことするにはCDATA Section使う。
  • muta244さんのライブラリを使えばもっと楽チンな気がするよ。
  • 簡単なopensocialアプリでも応用効くよ。。