aiRotaterをサンプルとした技術的解説
CEP(CreativeCloud以降のバージョンに搭載されているエクステンションエンジン)をターゲットとしたエクステンションは以下の様な構造になります。HTML5べーすとなりますので、基本的にWebアプリの構成と殆ど同一と言えるでしょう。違うのはJavascriptがアプリケーションに実装されたJavascriptをinvokeするというややこしい構造になっていると言う部分ぐらいなのです。まあ、アプリケーションに内蔵されたChrome上で実行されるのですからWebアプリと似ているというのは当然なのですが…
Panel root
├─css
│ ├─styles.css
│ └─theme.css
├─CSXS─manifest.xml
├─icons─aiRotater_default.png
├─index.html
├─js
│ ├─libs
│ │ ├─CSInterface-4.0.0.js
│ │ └─jquery-2.0.2.min.js
│ ├─main.js
│ └─themeManager.js
├─jsx
│ └hostscript.jsx
└─locale
├en_US
│ └─messages.properties
└ja_JP
└─messages.properties
ではcssから
*styles.css
#content {
margin-right:0px;
margin-left:0px;
vertical-align:middle;
width:100%;}
.debugtools{
background-color:#000;
position:absolute;
top:0px;
right:0px;
margin: 0;
padding: 0;
list-style-type: none;
text-align: center;}
.debugtools li {
display:inline;
margin:3px 0px 3px 0px;}
*theme.css
input[type="radio"] {
-webkit-appearance: none;
display:inline-block;
margin:2px;
width:12px;
height:12px;
background-color: #AAAAAA;
border-color: #AAAAAA;
border-style:solid;
border-radius:6px;}
input[type="radio"] + label{
display:inline-block;
vertical-align:super;}
input[type="radio"]:checked {
background-color: #333333;}
適当ですが、ごく一般的なCSSだと言うのがお分かり頂けるでしょう。
CSXSは後に回してiconsです。
こちらはpng形式のアイコンファイルを格納します。パネルがリトラクト状態の時に表示されるものです。
次にメインコンテンツであるindex.htmlです。
*index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link id="hostStyle" rel="stylesheet" href="css/theme.css"/>
<title>AI Rotater</title>
</head>
<body>
<p>
<button id="apply" data-locale="key1">apply</button>
<button id="aply_vrt" data-locale="key2">vertical</button>
</p>
http://js/libs/CSInterface-4.0.0.js
http://js/libs/jquery-2.0.2.min.js
http://js/themeManager.js
http://js/main.js
</body>
</html>
非常にシンプルです。CSS及びJavascriptの読み込みを除くとコンテンツ自体はボタン2つだけと言う構成です。実際のインターフェースは下のようなモノです。
どうしてボタンだけかというと、ローカライズ処理に対応する為で、その他の要素はダイナミックに生成する必要があるからです。ボタン等の一部のフォームに関連する部品ではdata-localeプロパティにてローカライズストリングを割り当てる事が出来ます。しかしながらプレーンなテキスト等はプロパティを持ちませんので、ローカライズストリングをJavascriptで読み込み、コンテンツ自体をダイナミックに生成するのです。
(function () {
'use strict';
var csInterface = new CSInterface();
var rb = csInterface.initResourceBundle();
document.write("<p>" + rb.key3 + ":<input type='text' value='0' id='offset' />");
document.write("<p><input type='checkbox' id='isLft' />" + rb.key4 + "<br />");
document.write("<input type='checkbox' id='rstRt' checked/>" + rb.key5 + "</p>");
function reloadPanel() {
location.reload();
}
function init() {
themeManager.init();
$("#apply").click(function () {
var ck = $("input#isLft").prop("checked") ? "true" : "false";
var rst = $("input#rstRt").prop("checked") ? "true" : "false";
csInterface.evalScript('$._aiRotater.apply('
+ $("input#offset").prop("value")
+ ', ' + ck + ', ' + rst + ', false)');
});
$("#aply_vrt").click(function () {
var ck = $("input#offset").prop("checked") ? "true" : "false";
var rst = $("input#rstRt").prop("checked") ? "true" : "false";
csInterface.evalScript('$._aiRotater.apply('
+ $("input#offset").prop("value")
+ ', ' + ck + ', ' + rst + ', true)');
});
$(document).keydown(function(e){
if(e.ctrlKey && e.keyCode == 90)
csInterface.evalScript('$._aiRotater.ud()');
if(e.ctrlKey && e.keyCode == 65) {
var ck = $("input#isLft").prop("checked") ? "true" : "false";
csInterface.evalScript('$._aiRotater.apply('
+ $("input#offset").prop("value")
+ ', ' + ck + ', false)');
}
if(e.ctrlKey && e.keyCode == 83) {
var ck = $("input#isLft").prop("checked") ? "true" : "false";
csInterface.evalScript('$._aiRotater.apply('
+ $("input#offset").prop("value")
+ ', ' + ck + ', true)');
}
});
}
init();
}());
続いて、main.jsを見て行きます。冒頭でCSInterfaceオブジェクトを生成しリソースバンドルを初期化しています。
var csInterface = new CSInterface();
var rb = csInterface.initResourceBundle();
document.write("<p>" + rb.key3 + ":<input type='text' value='0' id='offset' />");
document.write("<p><input type='checkbox' id='isLft' />" + rb.key4 + "<br />");
document.write("<input type='checkbox' id='rstRt' checked/>" + rb.key5 + "</p>");
ここですね。リーソースバンドルは初期化時にホストローケールを自動的に認識し、キー値を読込んでロケールに適した文字列を得る仕組みです。キー値の差換えですが、プロパティが存在するオブジェクトの場合はプロパティに直接キー値を渡す事で実現します。しかし、プレーンなHTML要素やヘルプドキュメントの場合、そうもいきません。こう言った場合はご覧の様にドキュメントのコンテンツをJavascriptでダイナミックにコントロールする事でマルチロケールなコンテンツを実現します。今回はdocumentに対するwriteメソッドを利用していますが、フレーム領域等のinnerHTMLに対してコンテンツを書き込んでも処理可能でしょう。
CEFなのでリロードによるパネルコンテンツのリフレッシュが利用出来ますが、このinitResourceBundle()はリロード時には動作しないので注意が必要です。
themeManager.init();
このファンクションはCreative Cloud Extension Builderで利用されるのもです。テーマカラーの変更はCSInterface.THEME_COLOR_CHANGED_EVENTイベントでトリガされます。それを捕まえてパネルのUIを追随させます。今回は解説しませんが、複雑な物ではありませんのでthemeManager.jsをのぞいて見て下さい。
$(document).keydown(function(e){
if(e.ctrlKey && e.keyCode == 90)
csInterface.evalScript('$._aiRotater.ud()');
if(e.ctrlKey && e.keyCode == 65) {
var ck = $("input#isLft").prop("checked") ? "true" : "false";
csInterface.evalScript('$._aiRotater.apply('
+ $("input#offset").prop("value")
+ ', ' + ck + ', false)');
}
この辺りのファンクションはキーイベント処理を行ないます。CEF上でHTMLを読み込みますので通常のHTML同様にキーイベントをキャプチャする事が可能です。
さて、肝心のExtendscriptとの橋渡しは
csInterface.evalScript('$._aiRotater.apply('
+ $("input#offset").prop("value")
+ ', ' + ck + ', ' + rst + ', false)');
こんな感じになっています。引数をかっこ内で組み立てているので複雑に見えるかも知れませんが、非常に単純です。Extendscriptのグローバルに登録された_aiRotaterのapplyメソッドをコールする様になっています。この例では返り値を取りませんが、csInterfaceクラス自体は非同期で構成されたライブラリ群ですので、コールバックを設定してもExtendscriptからの返り値を待ちません。ですから、ほとんどの場合evalScriptを抜けて処理が続行される場合、Extendscriptからのコールバックが間に合わずエラーを生じる事になります。このコールバック処理は意地でも第2引数内のコールバック関数上で処理する必要があります。本当はリスナを設定出来るのが良いのでしょうが、北京のエンジニアの回答はwaitで時間見計らって…と言う感じのコードでした。つかえない。
次はホストスクリプトです。
https://github.com/ten-A/Extend_Script_experimentals/blob/master/aiRotater.jsx
gitHubで公開されているものを参照ください。純粋なExtendscriptです。今回はコールバックは必要ありませんが、コールバック自体はコールバックファンクションの引数としてわたされる物です。単純なものだとStringで十分ですが、複雑なリターンの場合はペイロードのJSON化等を考慮した方がよいでしょう。
ロケール関連
messages.propertiesファイルはテキスト形式のファイルです。 内容は以下の様にkey+ナンバーと値を=で結びます。
key1=適用
key2=縦オブジェクト
key3=オフセット
key4=左ハンドルを基準に
key5=事前に回転角を初期化
CSXSですが、このフォルダはマニフェストファイルを収容する為の物というのは旧来から変わりません。HTMLパネルの場合はランタイムが4.0以降となります。対応アプリケーションはCC以降で、CS6には対応しません。
以下はaiRotaterのマニフェストです。
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionManifest Version="4.0" ExtensionBundleId="net.sytes.chuwa.aiRotater" ExtensionBundleVersion="1.0.0"
ExtensionBundleName="aiRotater" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Author><![CDATA[Ten]]></Author>
<Abstract><![CDATA[]]></Abstract>
<ExtensionList>
<Extension Id="com.ten-artai.aiRotater" Version="1.0" />
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<Host Name="ILST" Version="[17.0,18.9]" />
</HostList>
<LocaleList>
<Locale Code="All" />
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="4.0" />
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="net.sytes.chuwa.aiRotater">
<DispatchInfo >
<Resources>
<MainPath>./index.html</MainPath>
<ScriptPath>./jsx/hostscript.jsx</ScriptPath>
</Resources>
<Lifecycle>
<AutoVisible>true</AutoVisible>
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>AI Rotater</Menu>
<Geometry>
<Size>
<Height>250</Height>
<Width>240</Width>
</Size>
<MaxSize>
<Height></Height>
<Width></Width>
</MaxSize>
<MinSize>
<Height></Height>
<Width></Width>
</MinSize>
</Geometry>
<Icons>
<Icon Type="Normal">./icons/aiRotater_default.png</Icon>
</Icons>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>
また、最新のCC2014ではCEPエンジンが5.0にバージョンアップされました。大きな変更点はNode.jsに対応した事とNPMモジュールを利用出来る様になった事です。HTML5側でのデータ加工の幅が大きくなりました。また、CCにおいてもマイナーバージョンアップにてCEFの足回りが強化され、セッション関連の不具合が訂正されてIndexedDBやStorage、cookie等のローカルストレージが利用可能となっています。
最後にaiRotaterのパネルを載せておきます。
このようにローカライズストリングを読み込んで日本語のインターフェースとなります。
※UIのローカライズを処理してませんでしたので追記。以下のように各ロケールのmessages.propertiesファイルに対して「UI_menu_name」を設定します。
#en_US
key1=apply
key2=vertical
key3=offset
key4=Reference leftDirection.
key5=Reset angle before apply.
UI_menu_name=AI Rotater
ja_JP
key1=適用
key2=縦オブジェクト
key3=オフセット
key4=左ハンドルを基準に
key5=事前に回転角を初期化
UI_menu_name=AI ローテーター
すると
このようにUIも各ロケールに対応したものにローカライズされます。