2021年2月4日木曜日

2つのフォームで親子関係の表を更新する

複数の表をひとつのフォームや対話グリッドにて操作する方法については、以前にこちらの記事を書きました。今回はそうではなく、親子関係がある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からは作成しません。


アプリケーションの作成


続いてアプリケーション作成ウィザードを起動し、空のアプリケーション(グローバル・ページ、ホーム、ログイン・ページのみを含む)を作成します。アプリケーションの名称は任意です。今回は親子関係の表操作としました。


最初に表DAC_DOCUMENTSを対象としたレポートとフォームのページを作成します。最初に作成するページには、標準的な構成を適用します。その後、ページ作成ウィザードにて作成されたフォームのページに、表DAC_ATTACHMENTSを操作するコンポーネントを追加していきます。

アプリケーション・ビルダーよりページの作成をクリックし、ページ作成ウィザードを起動します。


コンポーネントに含まれるフォームを選択します。


フォーム付きレポートを選択します。


レポート・タイプ対話モード・レポートフォーム・ページ・モード標準を選びます。それ以外は任意ですが、これ以降の本記事の内容を読み替える必要が無くなるため、スクリーンショット通りの設定が望ましいです。特にフォーム・ページ番号にしましょう。ページ・アイテム名が変わってしまいます。に進みます。


ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択します。


データ・ソースとしてローカル・データベースソース・タイプで、表/ビューの名前として、DAC_DOCUMENTS(表)を選択します。に進みます。


主キー型主キー列の選択とし、主キー列として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_IDP3_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アクションの実行が行われます。


複数の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

とても助かりました。ありがとうございます。