Theatre.jsのStudio UIはブラウザの全画面を使うアプリケーションです。これは、Oracle APEXの非モーダル・ダイアログのページに実装します。非モーダル・ダイアログのページは、Oracle APEXのナビゲーションが一切生成されないため、Studio UIと競合することがありません。しかし、追加のUIを載せることもできないため、アニメーションをデータベースに保存するUIは、別ウィンドウに実装します。Studio UIの操作は、BroadcastChannelでイベントを送信して実行します。BroadcastChannelを使って非モーダル・ダイアログで処理を行う実装については、以前の記事「非モーダル・ダイアログにあるレポートをダイアログを開いたウィンドウから制御する」で紹介しています。
作成したAPEXアプリケーションは以下のように動作します。Theatre.jsのプロジェクトは、Getting StartedのWith HTML/SVGを流用しています。
ボタンExportをクリックして、その時点でのStudio UIのアニメーションをOracle Databaseに保存しています。ボタンReloadをクリックして、プロダクションのアニメーションを表示するページをリロードして初期化し、その後にボタンImportをクリックして、Oracle Databaseに保存されたアニメーションを表示しています。
https://github.com/ujnak/apexapps/blob/master/exports/sample-theatre-js.zip
アプリケーションが依存している表EBAJ_THEATREJS_PROJECTS(アニメーションを保存する表)およびパッケージEBAJ_ASP_THEATREJS_PKGを作成するDDLを、サポートするオブジェクトのインストール・スクリプトに含めているため、上記のファイルをインポートすると、アプリケーションとしては動作すると思います。
以下より作成したAPEXアプリケーションを紹介します。あくまで全画面を使うJavaScriptアプリをOracle Databaseと連携させる方法のサンプルなので、Theatre.jsとしての実用性はありません。プロジェクトについても、Getting Started With HTML/SVGの記述をファイルに直書きし、APEXのページに読み込んでいます。
最初にデータベースのオブジェクトを作成します。
表EBAJ_THEATREJS_PROJECTSを作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create table ebaj_theatrejs_projects ( | |
project_id varchar2(200 char) not null | |
constraint ebaj_theatrejs_projects_project_id_pk primary key, | |
project_state clob check (project_state is json) | |
); |
パッケージ定義EBAJ_ASP_THEATREJS_PKGを作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package ebaj_asp_theatrejs_pkg as | |
/** | |
* アプリケーションSample Theatre.jsに組み込むスクリプト。 | |
* | |
* 以下のDDLで作成される表EBAJ_THEATREJS_PROJECTSが操作の対象。 | |
--- | |
create table ebaj_theatrejs_projects ( | |
project_id varchar2(200 char) not null | |
constraint ebaj_theatrejs_projects_project_id_pk primary key, | |
project_state clob check (project_state is json) | |
); | |
--- | |
*/ | |
/** | |
* 指定したProject IDのアニメーションをJSONドキュメントとして | |
* HTTPバッファに出力する。 | |
*/ | |
procedure ajax_restore_project_state( | |
p_project_id in varchar2 | |
); | |
/** | |
* Theatre.jsのアニメーションをJSONドキュメントとして保存する。 | |
*/ | |
procedure ajax_save_project_state( | |
p_project_id in varchar2 | |
,p_project_state in clob | |
); | |
end ebaj_asp_theatrejs_pkg; | |
/ |
パッケージ本体を作成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package body ebaj_asp_theatrejs_pkg as | |
/** | |
* パッケージ本体 | |
*/ | |
/** | |
* projectIdを指定してprojectStateを取り出す。 | |
*/ | |
procedure ajax_restore_project_state( | |
p_project_id in varchar2 | |
) | |
as | |
l_project_state ebaj_theatrejs_projects.project_state%type; | |
l_response clob; | |
l_response_json json_object_t; | |
l_export apex_data_export.t_export; | |
begin | |
select project_state into l_project_state from ebaj_theatrejs_projects | |
where project_id = p_project_id; | |
/* レスポンスの作成 */ | |
l_response_json := json_object_t(); | |
l_response_json.put('status', true); | |
l_response_json.put('id', p_project_id); | |
l_response_json.put('state', l_project_state); | |
l_response := l_response_json.to_clob(); | |
/* レスポンスの出力 */ | |
l_export.mime_type := 'application/json'; | |
l_export.as_clob := true; | |
l_export.content_clob := l_response; | |
apex_data_export.download( p_export => l_export ); | |
exception | |
when no_data_found then | |
l_response := json_object( | |
'status' value false | |
,'reason' value SQLERRM | |
); | |
htp.p(l_response); | |
end ajax_restore_project_state; | |
/** | |
* projectStateを保存する。 | |
*/ | |
procedure ajax_save_project_state( | |
p_project_id in varchar2 | |
,p_project_state in clob | |
) | |
as | |
l_exist number; | |
l_response clob; | |
begin | |
begin | |
select 1 into l_exist from ebaj_theatrejs_projects | |
where project_id = p_project_id; | |
/* 保存されているProject IDが無ければ例外ハンドラでINSERTする。 */ | |
update ebaj_theatrejs_projects set project_state = p_project_state | |
where project_id = p_project_id; | |
exception | |
when no_data_found then | |
insert into ebaj_theatrejs_projects(project_id, project_state) | |
values(p_project_id, p_project_state); | |
end; | |
l_response := json_object( | |
'status' value true | |
,'message' value 'projectState is saved.' | |
); | |
htp.p(l_response); | |
exception | |
when others then | |
l_response := json_object( | |
'status' value false | |
,'reason' value SQLERRM | |
); | |
htp.p(l_response); | |
end ajax_save_project_state; | |
end ebaj_asp_theatrejs_pkg; | |
/ |
パッケージに含まれているプロシージャは、Oracle APEXのAjaxコールバックとして呼び出します。
APEXのアプリケーションを作成します。名前はSample Theatre.jsとします。
ホーム・ページにプロダクションのアニメーションを表示するリージョンと、Theatre.jsのアニメーションをデータベースに保存するボタンEXPORTと、データベースからアニメーションを取り出すボタンIMPORTを作成します。また、ページをリロードするボタンRELOADも作成します。
JavaScriptのコードは別ファイルに記述します。ページ・プロパティのJavaScriptのファイルURLに以下を記述します。
[module]#APP_FILES#js/production-animation-tutorial#MIN#.js
ファイルproduction-animation-tutorial.jsの内容です。
ボタンEXPORT、RELOAD、IMPORTの処理はAPEXアクションとして実装しています。データベースからのアニメーションの取り出しは、AjaxコールバックRESTORE_PROJECT_STATEの呼び出しによって行います。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'https://cdn.jsdelivr.net/npm/@theatre/browser-bundles@0.7.2/dist/core-only.min.js'; | |
/* | |
* プロジェクトの定義はプロダクションのページと共有するために、別ページにファンクションとして | |
* 定義する。 | |
*/ | |
import prepareProject from './project.js'; | |
// We can now access just Theatre.core from here | |
const { core } = Theatre; | |
const projectId = apex.item("P1_PROJECT_ID").getValue(); | |
apex.debug.info("projectId: ", projectId); | |
/* | |
* Theatre.jsのアニメーションを表示する処理。 | |
* | |
* ボタンIMPORTを押した時に実行されるように、ファンクションにしている。 | |
*/ | |
async function restoreProjectState(projectId ) { | |
/* | |
* アニメーションを定義しているJSONをデータベースから取り出す。 | |
*/ | |
const response = await apex.server.process( | |
"RESTORE_PROJECT_STATE", | |
{ | |
pageItems: "#P1_PROJECT_ID" | |
} | |
); | |
const projectState = JSON.parse(response.state); | |
apex.debug.info("projectState: ", projectState); | |
/* | |
* アニメーションを実行する。 | |
*/ | |
const r = prepareProject(core, projectId, projectState); | |
const project = r.project; | |
const sheet = r.sheet; | |
// wait for project to be ready | |
project.ready.then(() => { | |
apex.debug.info('project is ready', project); | |
sheet.sequence.play({ iterationCount: Infinity }) | |
}); | |
}; | |
/* | |
* Theatre.jsのアニメーションのエクスポートとインポートを行う。 | |
*/ | |
const channel = new BroadcastChannel('control-theatre'); | |
// エクスポートの成功をP1_STATUSに表示する。 | |
channel.addEventListener("message", (event) => { | |
apex.debug.info("status: ", event); | |
if ( event.data.type === 'status' ) { | |
apex.item("P1_STATUS").setValue(event.data.message); | |
} | |
}); | |
const controlElement = document.getElementById("CONTROLS"); | |
const controls = apex.actions.createContext("controls", controlElement); | |
controls.add([ | |
{ | |
// EXPORTはStudioが実装されているページで処理する。 | |
name: "EXPORT", | |
action: (event, element, args) => { | |
channel.postMessage( { type: "export" } ); | |
} | |
}, | |
{ | |
// ページをリロードしてアニメーションを初期化する。 | |
name: "RELOAD", | |
action: (event, element, args) => { | |
window.location.reload(); | |
apex.item("P1_STATUS").setValue(null); | |
} | |
}, | |
{ | |
// IMPORTはこのページで処理する。 | |
name: "IMPORT", | |
action: (event, element, args) => { | |
restoreProjectState( projectId ); | |
apex.item("P1_STATUS").setValue("projectState is restored."); | |
} | |
} | |
]); |
インポートしているファイルproject.jsでは、Theatre.jsのプロジェクトを記述しています。このファイルはStudio UIを実装しているJavaScriptファイルにも読み込みます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 以下はTheatre.jsのGetting StartedのWith HTML/SVGのコード | |
* https://www.theatrejs.com/docs/latest/getting-started/with-html-svg | |
*/ | |
export default function prepareProject( core, projectId, projectState ) { | |
const project = core.getProject( projectId, { | |
state: projectState | |
}); | |
const sheet = project.sheet('Sheet 1'); | |
const obj = sheet.object('Heading 1', { | |
y: 0, | |
opacity: core.types.number(1, { range: [0, 1] }), | |
}); | |
const articleHeadingElement = document.getElementById('article-heading'); | |
// animations | |
obj.onValuesChange((obj) => { | |
articleHeadingElement.style.transform = `translateY(${obj.y}px)` | |
articleHeadingElement.style.opacity = obj.opacity | |
}); | |
return { project: project, sheet: sheet, obj: obj }; | |
} |
AjaxコールバックRESTORE_PROJECT_STATEでは、以下のコードを実行します。
ebaj_asp_theatrejs_pkg.ajax_restore_project_state(
p_project_id => :P1_PROJECT_ID
);
ソースのHTMLコードにアニメーションを適用するH1要素を記述します。
<h1 id="article-heading" style="text-align: center">Welcome</h1>
外観のテンプレート・オプションにより、リージョンの高さを320pxにしています。ボタンを配置しているリージョンをリージョンの上に移動し、リージョンの高さをもう少し高くしても良いかもしれません。
ページ・アイテムP1_PROJECT_IDにプロジェクトIDを設定します。ソースのタイプにアイテム、アイテムとしてG_PROJECT_IDを設定しています。つまり、このページ・アイテムの値はアプリケーション・アイテムG_PROJECT_IDに設定した値になります。
このようにしている理由は、ページ・アイテムの値はJavaScriptのコードから簡単に参照できるためです。Stuido UIのページでも同じプロジェクト名を参照しますが、ページが異なるためJavaScriptのコードからはP1_PROJECT_IDの値は参照できません。そのため、Studio UIを実装したページでは、ページ・アイテムとしてP2_PROJECT_IDを作成し、そのソースをアプリケーション・アイテムG_PROJECT_IDにしています。結果として、双方のページで、アプリケーション・アイテムG_PROJECT_IDの値をプロジェクト名として参照します。
共有コンポーネントのアプリケーション・アイテムとしてG_PROJECT_IDを作成します。
アプリケーション・アイテムへの値の設定は、アプリケーションの計算により行います。
頻度の計算ポイントとして新規インスタンス(新規セッション)開始時を選択し、計算タイプとして静的割り当て、計算にHTML Animation Tutorialを設定します。
Theatre.jsのプロジェクトがファイルproject.jsに直書きされていることや、アプリケーション・アイテムに設定しているプロジェクト名が決め打ちなので、このアプリケーション自体はTheatre.jsのGetting StartedのWith HTML/SVGで説明されている以上のことはできません。
ボタンをクリックして実行した処理のステータスを表示するページ・アイテムとしてP1_STATUSを作成します。
ボタンを配置する静的コンテンツのリージョンを作成します。外観のテンプレートにButtons Containerを選択します。APEXアクションのコンテキストを作成する際にJavaScriptからリージョンを選択できるよう、詳細の静的IDとしてCONTROLSを設定します。
プロダクションのアニメーションの表示と、それをコントロールするページの実装は以上で完了です。
Theatre.jsのStudio UIは、外観のページ・モードを非モーダル・ダイアログとしたページに実装します。
ページ・プロパティのJavaScriptのファイルURLとして以下を記述します。
[module]#APP_FILES#js/theatre-studio#MIN#.js
インポートするtheatre-studio.jsの内容です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'https://cdn.jsdelivr.net/npm/@theatre/browser-bundles@0.7.2/dist/core-and-studio.js'; | |
/* | |
* プロジェクトはプロダクションのページと共有するため、別ページにファンクションとして | |
* 定義する。 | |
*/ | |
import prepareProject from './project.js'; | |
// We can now access Theatre.core and Theatre.studio from here | |
const { core, studio } = Theatre; | |
/* | |
* プロジェクトIDは、アプリケーション・アイテムG_PROJECT_IDに定義されている。 | |
*/ | |
const projectId = apex.item("P2_PROJECT_ID").getValue(); | |
apex.debug.info("projectId: ", projectId); | |
/* | |
* データベースに保存されているアニメーションを取り出して、 | |
* それでStudioを初期化する。 | |
*/ | |
apex.server.process( | |
"RESTORE_PROJECT_STATE", | |
{ | |
pageItems: "#P2_PROJECT_ID" | |
}, | |
{ | |
success: (data) => { | |
const projectState = JSON.parse(data.state); | |
/* | |
* Theatre.jsのStudioを開始する。 | |
*/ | |
studio.initialize(); // Start the Theatre.js UI | |
/* | |
* Theatre.jsのStudioの起動時は、アニメーションのデータをlocalStorageから | |
* 取り出す。DBから取り出したprojectStateを与えると、Studioは | |
* ブラウザとディスクのデータでConflictがあると報告する。 | |
* ブラウザとディスクのどちらを使うか選択できるので、DBのデータを使う場合は | |
* ディスク、localStorageを使う場合はブラウザを選択する。 | |
*/ | |
apex.debug.info("initial projectStage: ", projectState); | |
const r = prepareProject(core, projectId, projectState); | |
/* | |
* アニメーションはブラウザのlocalStorageから取り出すので、project自体は | |
* 初期化以後、参照することがない。 | |
*/ | |
} | |
} | |
) | |
/* | |
* BroadcastChannelからイベントを受け取って、アニメーションをエクスポートする。 | |
* 本サンプルで追加したデータベースへアニメーションを保存する処理。 | |
*/ | |
const channel = new BroadcastChannel('control-theatre'); | |
channel.addEventListener("message", (event) => { | |
apex.debug.info("event: ", event); | |
if (event.data.type === "export") { | |
/* | |
* 現在のアニメーションの設定をJSONドキュメントとして取り出す。 | |
* サーバーに送信するためにページ・アイテムP2_PROJECT_STATEに | |
* 設定する。 | |
*/ | |
const projectState = studio.createContentOfSaveFile(projectId); | |
apex.debug.info("projectState: ", projectState); | |
apex.item("P2_PROJECT_STATE").setValue(JSON.stringify(projectState)); | |
/* | |
* アニメーションをデータベースに保存する。 | |
*/ | |
apex.server.process( | |
"SAVE_PROJECT_STATE", | |
{ | |
pageItems: "#P2_PROJECT_ID,#P2_PROJECT_STATE" | |
}, | |
{ | |
success: (data) => { | |
apex.debug.info(data); | |
channel.postMessage( { type: "status", message: data.message } ); | |
} | |
} | |
) | |
} | |
}); |
データベースからアニメーションを取り出すためにAjaxコールバックRESTORE_PROJECT_STATEを呼び出し、保存するためにSAVE_PROJECT_STATEを呼び出します。RESTORE_PROJECT_STATEは先のページに実装したものと同じ処理ですが、ページが異なるため、同じAjaxコールバックを作成する必要があります。
AjaxコールバックSAVE_PROJECT_STATEでは、以下のコードを実行します。
ebaj_asp_theatrejs_pkg.ajax_save_project_state(
p_project_id => :P2_PROJECT_ID
,p_project_state => :P2_PROJECT_STATE
);
ページ・アイテムP2_PROJECT_IDは、先ほどのP1_PROJECT_IDと同様に、ソースのアイテムとしてG_PROJECT_IDを設定します。
静的コンテンツのリージョンを作成し、ソースのHTMLコードにアニメーションの対象とするH1要素を記述します。
<h1 id="article-heading" style="text-align: center">Welcome</h1>
以上でStudio UIの実装も完了です。
Studio UIはナビゲーション・メニューから開きます。ページ作成ウィザードでナビゲーション・メニューの作成をオンにしていると、自動的にダイアログを開くメニュー・エントリが作成されます。
アプリケーションの背景を黒にするため、テーマ・ローラーを開き、テーマとしてVita - Darkを選択しています。
作成したアプリケーションの紹介は以上になります。
ブラウザとディスクでアニメーションに差異がある場合の、Studio UIの画面です。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完