複数の表をひとつのフォームや対話グリッドにて操作する方法については、以前にこちらの記事を書きました。今回はそうではなく、親子関係がある2つの表があり、それぞれにフォーム・リージョンを同じページ上に作成し、操作を行います。
スキーマの作成
以下をモデルとして、クイックSQLにて表を作成します。親となる表DAC_DOCUMENTSに紐づく複数のアタッチメントが表DAC_ATTACHMENTSに保存されます。
# prefix: dac
# semantics: default
# prefixPKwithTname: true
documents
document_name vc80
attachments
document_id /fk documents
attachment_name vc80
attachment file
SQLの生成を行い、生成されたSQLのレビューおよび実行をすることにより表を2つ作成します。アプリケーションは別途作成するため、クイックSQLからは作成しません。
アプリケーションの作成
続いてアプリケーション作成ウィザードを起動し、空のアプリケーション(グローバル・ページ、ホーム、ログイン・ページのみを含む)を作成します。アプリケーションの名称は任意です。今回は親子関係の表操作としました。
フォーム付きレポートを選択します。
レポート・タイプは対話モード・レポート、フォーム・ページ・モードは標準を選びます。それ以外は任意ですが、これ以降の本記事の内容を読み替える必要が無くなるため、スクリーンショット通りの設定が望ましいです。特にフォーム・ページ番号は3にしましょう。ページ・アイテム名が変わってしまいます。次に進みます。
ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択します。
主キー型を主キー列の選択とし、主キー列としてDAC_DOCUMENT_ID(Number)を選択します。作成をクリックします。
標準的な設定により、表DAC_DOCUMENTSを扱う対話モード・レポートとフォームのページが作成されました。実行して動作を確認してみましょう。
データが登録されていないので、対話モード・レポートにデータが見つかりません。と表示されています。作成をクリックします。
フォームのページが開きます。Document Nameを指定し、作成をクリックします。今回はこのフォームのページに、アタッチメントの一覧を表示する対話グリッドのリージョンと、アタッチメントの作成/編集/削除を行うフォームのリージョンを追加します。
作成した行がレポートに表示されます。
以上で、準備は完了です。
アタッチメントを操作するフォーム・リージョンの追加
ドキュメント編集のページ(ページ番号は3のはず)を開いて、フォーム・リージョンをドキュメント編集の下に追加します。
追加したリージョンの名前をアタッチメント編集とします。フォームを追加した時点でヘッダーの前に作成されるプロセスの名前を、初期化フォームアタッチメント編集に変更します。ソースの表名にはDAC_ATTACHEMENTSを選択します。
フォームに含まれるページ・アイテムのプロパティを調整します。
ページ・アイテムP3_DAC_ATTACHMENT_IDを選択します。こちらは主キーであり、利用者が入力することがないため、タイプを非表示にします。また、動的アクションによって設定するため、保護された値をOFFにします。また、主キーについてはONにします。
P3_DOCUMENT_IDはアタッチメントを親表の行に紐づけるために使用します。親表の主キーDAC_DOCUMENT_IDを参照します。こちらも利用者が直接指定することはないので、タイプを非表示にします。この値も動的アクションにて設定するため、保護された値をOFFにします。
P3_ATTACHMENTを選択します。このアイテムはファイルをバイナリ・データ(BLOB)として保存します。アップロードするファイルを選択するため、タイプをファイル参照...にします。記憶域タイプはBLOB column specified in Item Source attributeを選びます。Item Sourceはプロパティのソースの設定であり、このページ・アイテムにはデフォルトで列ATTACHMENTが設定されています。保存するファイルのそれぞれの属性であるMIMEタイプ列、ファイル名列、文字セット列、BLOB最終更新列として、ATTACHMENT_MIMETYPE、ATTACHMENT_FILENAME、ATTACHMENT_CHARSET、ATTACHMENT_LASTUPDを指定します。
アップロードするファイルの属性情報は利用者が指定することはないので、すべてのページ・アイテムを選択し、サーバー側の条件のタイプをなしに設定します。このように設定することにより、これらのページ・アイテムがページ上に生成されなくなります。
続けて、アタッチメントの登録、更新、削除を行うボタンを作成します。
登録のためのボタンを作成します。ボタン名はB_CREATE_ATTACH、名前は登録とします。データベース・アクションはSQL INSERT操作を指定します。
更新のためのボタンを作成します。ボタン名はB_UPDATE_ATTACH、名前を更新とします。データベース・アクションはSQL UPDATE操作を指定します。
削除のためのボタンを作成します。ボタン名はB_DELETE_ATTACH、名前を削除とします。データベース・アクションはSQL DELETE操作を指定します。
一旦、ページへの変更を保存し、ページを実行してみます。アタッチメント編集というフォーム・リージョンが追加されていることが確認できます。
フォームが作成されたので、登録、更新、削除の実際の処理を実装していきます。アタッチメント編集のリージョンのテンプレートをInline Dialogに変更します。アタッチメントの操作を行う際にダイアログとして開くことにより、同時にドキュメントを操作をできないよう制限します。
ドキュメント編集のリージョンに、Inline Dialogになったアタッチメント編集を開くボタンを追加します。ボタン名をB_OPEN_ATTACH、名前をアタッチメントの追加とし、動作のアクションを動的アクションで定義に設定します。
ボタンB_OPEN_ATTACHに動的アクションを追加します。追加する動的アクションの名前は、アタッチメント編集を開くとします。タイミングについてはデフォルトでボタンのクリックになります。このボタンからアタッチメント編集のリージョンを開いた際には、つねにファイルの登録作業のみを行うようにします。
Trueアクションを追加します。見やすくなるように左ペインを動的アクション・ビューに切り替えます。
アクションとしてクリアを作成します。選択タイプはアイテムとし、クリアするアイテムとして、P3_DAC_ATTACHMENT_ID、P3_DOCUMENT_IDの2つを指定します。表DAC_ATTACHEMENTSの主キーとなるDAC_ATTACHMENT_IDの値と、表DAC_DOCUMENTSの主キーを参照しているDOCUMENT_IDを未設定とすることで、それぞれレコードの保存時に新規で生成されます。
登録を行うボタンであるB_CREATE_ATTACHは(アクションを)表示にします。
更新を行うボタンであるB_UPDATE_ATTACHは(アクションを)非表示にします。
削除を行うボタンであるB_DELETE_ATTACHも(アクションを)非表示にします。
準備ができたので、アタッチメント編集のリージョンを開きます。アクションはリージョンを開く、選択タイプとしてリージョンを選び、リージョンにアタッチメント編集を指定します。
これで、アタッチメントの追加をクリックすることで、アタッチメントを登録するダイアログを開く動的アクションが完成しました。実際にページを動かしてみましょう。
アタッチメントの追加をクリックすると、アタッチメント編集のダイアログが開くところまで確認できます。
アタッチメントの操作を行うプロセスの追加
表DAC_DOCUMENTSを操作するプロセスはそのまま使います。プロセスのプロパティである挿入後に主キーを返すがONになっていることを確認します。表DAC_DOCUMENTSへの新規行の挿入と同時に表DAC_ATTACHMENTSに行を挿入する場合、このプロセスから返されるページ・アイテムP3_DAC_DOCUMENT_IDを表DAC_ATTACHMENTSの列DOCUMENT_IDとして設定します。
すでに表DAC_DOCUMENTSに行が存在し、操作が表DAC_ATTACHMENTSへの行の追加、更新、削除に限定される場合、このプロセスが呼び出されないようにサーバー側の条件を設定します。タイプはファンクション本体とし、以下のPL/SQLファンクション本体を設定します。
CREATE, SAVE, DELETEのボタンはドキュメントの操作を行うボタンで、B_CREATE_ATTACHはアタッチメントの追加ですが、P3_DAC_DOCUMENT_IDすなわち親のレコードが存在しないときだけは、親表であるDAC_DOCUMENTSへの行の挿入を行う、という条件です。
begin if :REQUEST in ('CREATE', 'SAVE', 'DELETE') then return true; end if; if :REQUEST = 'B_CREATE_ATTACH' and :P3_DAC_DOCUMENT_ID is null then return true; end if; return false; end;
次に新規にプロセスを作成します。名前をドキュメントIDの指定とし、タイプはコードを実行、実行するPL/SQLコードは以下になります。
:P3_DOCUMENT_ID := :P3_DAC_DOCUMENT_ID;
表DAC_DOCUMENTSを処理するプロセスが使用した主キーが必ず、表DAC_ATTACHMENTSのDOCUMENT_IDになります。
フォーム・リージョンであるアタッチメント編集の処理を行うプロセスを追加します。新規にプロセスを作成します。名前をプロセス・フォームアタッチメント編集とし、タイプはフォーム - 行の自動処理(DML)を選択します。フォーム・リージョンはアタッチメント編集です。このプロセスはドキュメント編集の際には実行しないように、サーバー側の条件として、タイプをリクエストは値に含まれない、値はCREATE SAVE DELETEの3つのボタンを指定します。
アタッチメントが削除された際に、P3_DAC_ATTACHMENT_IDにキーとなる値が設定されたままだと、ページの再ロード時にORA-1403 - データが見つかりません。というエラーが発生するため、ページ・アイテムP3_DAC_ATTACHMENT_IDを初期化します。
プロセスを作成し、名前をアタッチメント削除の対応とします。タイプはセッション・ステートのクリアを選択します。設定のタイプはClear Items、アイテムとしてはP3_DAC_ATTACHMENT_IDを選択します。アタッチメントが削除されたときのみ実行するので、サーバー側の条件として、ボタン押下時にB_DELETE_ATTACHを設定します。
最後にドキュメントの作成、編集、削除を行ったときは、対話モード・レポートのページに戻り、アタッチメントの操作ではフォームのページを開き続けるようにします。
ブランチのページに移動2を選択し、サーバー側の条件として、タイプにリクエストは値に含まれるを選択し、値としてCREATE SAVE DELETEを設定します。
以上でアタッチメントを追加するプロセスの実装が完了しました。
アタッチメントの追加を行ってみます。フォームのページを実行します。レポートから呼び出していないので、ドキュメントは新規登録になります。Document Nameを指定し、アタッチメントの追加をクリックします。開いたダイアログにて、Attachment NameおよびアップロードするファイルをAttachmentとして選択します。以上の指定を行い、ダイアログ上の登録ボタンをクリックします。
行が作成されました。とメッセージが表示されますが、実際にアタッチメントが登録されたのか確認できません。
続いて、ドキュメントに紐づくアタッチメントをリストする対話グリッドを作成します。
アタッチメントをリストする対話グリッドの追加
新たに対話グリッドのリージョンを、アタッチメント編集の下に追加します。
名前はアタッチメント一覧とし、ソースのタイプは表/ビュー、表名にDAC_ATTACHMENTSを指定します。WHERE句に以下を設定し、送信するページ・アイテムとして、P3_DAC_DOCUMENT_IDを設定します。
DOCUMENT_ID = :P3_DAC_DOCUMENT_ID
認識された列のプロパティを調整します。
列DAC_ATTACHMENT_IDを選択し、タイプを非表示、主キーをONに変更します。
列DOCUMENT_IDのタイプを非表示にします。
対話グリッドではBLOBは扱えず、含まれるとエラーが発生します。列ATTACHMENTを選択し、サーバー側の条件のタイプをなしに設定することにより、取り扱い対象から外します。
列の調整は以上です。
作成した対話グリッドに動的アクションを設定可能にするため、対話グリッドを編集可能に変更します。
対話グリッドのAttributesを開き、編集の有効をONにします。ただし、編集を有効にする理由は動的アクションを登録するためだけで、操作はすでに登録済みのアタッチメント編集のフォーム・リージョンにて実施します。実行可能な操作の行の追加、行の更新、行の削除はすべてチェックを外します。
編集を有効にすることで追加された列APEX$ROW_SELECTORを選択し、設定の複数選択の有効化をOFFにします。対話グリッド上で選択可能な行を1行のみに制限し、その1行が選択されたときに、動的アクションによってアタッチメント編集のダイアログを開きます。
対話グリッドの編集操作は行わないので、列APEX$ROW_ACTIONを選択し、サーバー側の条件をなしにします。
アタッチメント一覧から、特定のアタッチメントを選択したときにダイアログを開く動的アクションを作成します。アタッチメント一覧の上でコンテキスト・メニューを表示させ、動的アクションの作成を実行します。
複数のTrueアクションを登録します。
最初に選択されたアタッチメントの情報をフォーム・リージョンであるアタッチメント編集のページ・アイテムに設定するために、JavaScriptコードを実行します。アクションとしてJavaScriptコードの実行を選択し、設定のコードに以下のJavaScriptコードを設定します。
let model = this.data.model; let record = this.data.selectedRecords[0]; let attachment_id = model.getValue(record, "DAC_ATTACHMENT_ID"); let document_id = model.getValue(record, "DOCUMENT_ID"); let attachment_name = model.getValue(record, "ATTACHMENT_NAME"); $s("P3_DAC_ATTACHMENT_ID", attachment_id); $s("P3_DOCUMENT_ID", document_id); $s("P3_ATTACHMENT_NAME", attachment_name);
登録を行うボタンであるB_CREATE_ATTACHは(アクションを)非表示にします。
更新を行うボタンであるB_UPDATE_ATTACHは(アクションを)表示にします。
削除を行うボタンであるB_DELETE_ATTACHは(アクションを)表示にします。
準備ができたので、アタッチメント編集のリージョンを開きます。アクションはリージョンを開く、選択タイプとしてリージョンを選び、リージョンにアタッチメント編集を指定します。
対話グリッドが最初に開いたときに、デフォルトで先頭行が選択されることを抑制するために、Attributesの詳細に含まれるJavaScript初期化コードに以下を設定します。
function(config){ config.initialSelection = false; return config; }
これで、アタッチメント一覧からアタッチメントを選択することで、アタッチメントの更新と削除を行うダイアログを開く動的アクションが完成しました。
以上ですべての実装が完了です。動作を確認してみましょう。最初に載せてあるGIF動画のような操作が可能になっているはずです。
ラベルやテンプレート・オプションなど、見栄えに関する部分には一切手を入れていませんし、また、ページ・アイテムの初期化など、調整できる点は多々あります。それでも、このような動作をするアプリケーションを作る方法について、感じが掴めたのではないでしょうか。
今回作成したアプリケーションのエクスポートをこちらに置きました。
https://github.com/ujnak/apexapps/blob/master/exports/parent-child-table-ops.sql
Oracle APEXを使ったアプリケーション開発の一助になれば幸いです。
謝辞
対話グリッドを開いた際に、行の選択を行わない設定について、OndoorのVikas Pandeyさんの記事 3 Ways to Unselect Default Checkbox in Interactive Grid Oracle APEXを参考にさせていただきました。
https://blogs.ontoorsolutions.com/post/3-ways-to-unselect-all-checkbox-in-ig/
選択した対話グリッドの列の値を取り出し、ページ・アイテムに設定する方法については、YouTubeのOracle Apex & DatabaseチャンネルのSelect specific row from interactive grid in oracle apexを参考にさせていただきました。
https://www.youtube.com/watch?v=hBDlZYIzIeo
とても助かりました。ありがとうございます。
完