e-learning、オラクル研修、LMS(学習管理システム)のiStudy

e-learning、オラクル研修、LMS(学習管理システム)のiStudy

第135回「実用WEBアプリ 文書管理システム(16) 複数選択によるディレクトリ移動(その1)」

2015.08.06

こんにちは。インストラクターの蓑島です。

今回と次回で文書やディレクトリを複数選択してそれらを一括で、他のディレクトリに移動させる処理を実装します。個々の更新画面で所属先のディレクトリを変更することはできますが、やはり複数選択して一括でディレクトリを移動できれば大変便利です。

今回はそのためのフォーム画面を作成し、次回でフォームの送信先処理の実装をします。

そのフォーム画面ですが、新規で作成するのではなく、既存のディレクトリを表示する画面(DOC_SHOW)を修正します。DOC_SHOWプロシージャは指定された番号が文書の番号である場合はその文書の内容を表示し、ディレクトリの番号である場合は、そのディレクトリ内の文書やサブディレクトリを一覧で表示します。その一覧にチェックボックスを設けて、各文書やディレクトリをチェック可能とし、チェックしたものを一括で他のディレクトリに移動させる、そんな処理にしたいと思います。

早速、DOC_SHOWプロシージャをそのように修正しましたので、以下の画面ショットをご覧ください。

例えば私の環境では、8番のレコードは「PL/SQLの勉強ノート」という名前のディレクトリですがこれをDOC_SHOWで表示します。

バックナンバー 第103回「WEBアプリ作成(1) (Oracle DBとPL/SQLだけで、即、WEBアプリ)」と同等の設定ができていれば、以下のURLです。

http://localhost:8080/dad/doc_show?P_ID=8

初回にログインを求められますがスキーマユーザでログインします。(私の場合、SCOTTユーザ)

表示されたディレクトリ画面にはチェックボックスや移動先ディレクトリ番号、移動ボタンなどが追加されています。

移動したい文書やディレクトリをチェックして、移動先のディレクトリ番号を入力して、移動ボタンをクリックすることで移動できるようにしますが、移動処理そのものは次回実装します。ですから今回の時点では移動ボタンをクリックしても、エラーとなります。

また、一覧のヘッダー部分のチェックボックスは特別なチェックボックスにします。ここをクリックすれば、全チェックボックスを選択したり解除したりできます。
大量の選択処理では必須ですね。なおこの機能そのものはJavaScriptですがPL/SQLから出力します。

スクリーンショットはここまでにして、次に修正後DOC_SHOWプロシージャのソースコードを確認します。

注意)以下のソースコードはブラウザ表示のために、山括弧(「<」と「>」)を全角にしています。 コピーして実行する方は、必ず、すべての山括弧(「<」と「>」)を半角に変換してください。

SQL> SHOW USER
ユーザーは"SCOTT"です。 -- 私の場合、スキーマユーザはSCOTT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
CREATE OR REPLACE
PROCEDURE DOC_SHOW
(P_ID  IN VARCHAR2 DEFAULT NULL )
IS
/*****************************************/
/*  文書・ディレクトリ  表示処理         */
/*****************************************/
   V_ID NUMBER;  -- 文書(またはディレクトリ)の番号
   V_ERRMSG   VARCHAR2(1000);   -- エラーメッセージ
   REC         DOCS%ROWTYPE;    -- DOCS表の1行を格納する変数
   USER_ERROR     EXCEPTION;    -- ユーザ定義例外
/************************************************/
/*  文書を表示するローカルプロシージャ          */
/************************************************/
   PROCEDURE  DOC_DISPLAY(REC1 IN DOCS%ROWTYPE)
   IS
   BEGIN
      HTP.P( '<HTML>' );
      -- タイトルをセット
      HTP.P( '<HEAD><TITLE>' || REC1.TITLE || '</TITLE></HEAD>' );
      HTP.P( '<BODY>' );
      -- メニュバーをセット
      HTP.P(DOC_FUNC_MENU(REC1.ID));
      HTP.P( '<FONT COLOR="BLUE"><I>' ||REC1.TITLE || '</I></FONT>'
      || '<FONT SIZE="-1">['   --タイトルの横に更新日時をセット
      ||TO_CHAR(REC1.UPDATE_DATE, 'YYYY/MM/DD HH24:MI' ) || ']</FONT>' );
      -- 本文は改行を含むので、PREタグで囲み、そのままを表示する
      HTP.P( '<PRE>' || REC1.HONBUN || '</PRE>' );
      HTP.P( '</BODY>' );
      HTP.P( '</HTML>' );
   END DOC_DISPLAY;
/************************************:***********/
/*  ディレクトリを表示するローカルプロシージャ  */
/************************************************/
   PROCEDURE  DIRECTORY_DISPLAY(REC2 IN DOCS%ROWTYPE)
   IS
      TYPE CUR_DOCS_TYPE IS REF CURSOR ;   -- 型の宣言
      CUR_DOCS   CUR_DOCS_TYPE;           -- その型を使って、カーソル変数を宣言
      REC_DOCS DOCS%ROWTYPE;
      V_TITLE  DOCS.TITLE%TYPE;
   BEGIN
   /************************************************/
   /*   動的SQLを使った、明示カーソルのオープン    */
   /************************************************/
      IF REC2.ID IS NULL THEN  -- パラメータが指定されていないとき、(OYA_DIR IS NULL)
         OPEN CUR_DOCS FOR 'SELECT * FROM DOCS WHERE OYA_DIR IS NULL ORDER BY SEQ' ;
         V_TITLE := '最上位ディレクトリの一覧' ;
      ELSE  -- パラメータが指定されているとき (OYA_DIR = :1)
         OPEN CUR_DOCS FOR 'SELECT * FROM DOCS WHERE OYA_DIR = :1 ORDER BY SEQ' USING REC2.ID;
         V_TITLE := REC2.TITLE;
      END IF;
      HTP.P( '<HTML>' );
      HTP.P( '<HEAD><TITLE>' || V_TITLE|| '</TITLE></HEAD>' );
      HTP.P( '<BODY>' );
      -- メニューバーの追加
      HTP.P(DOC_FUNC_MENU(REC2.ID));
      -- ディレクトリのタイトルを表示
      HTP.P( '<FONT COLOR="BLUE"><I>' || V_TITLE || '</I></FONT>' );
      -- このディレクトリ以下の文書とサブディレクトリを表示
      -- 表示する文章とサブディレクトリ全体をFORMタグで囲む  2015/08/06 ★★★
      HTP.P( '<FORM name="fname" ACTION="doc_dir_man_exe" METHOD="GET">' );
      HTP.P( '<INPUT TYPE="HIDDEN" NAME="P_SEL_ID" VALUE="DUMMY">' );   -- ★★★
      HTP.P( '<TABLE BORDER>' );
      HTP.P( '<TR BGCOLOR="LIGHTYELLOW"><TD>ID</TD><TD>#</TD><TD>タイトル</TD><TD>更新日</TD>' ||
      -- 追加した列 2015/08/06★★★
      '<TD><INPUT TYPE="CHECKBOX" NAME="aaa" onClick="AllChecked();" /></TD>' ||
      '</TR>' );
   /************************************************/
   /*   カーソル変数を使った、行の取り出し( FETCH )  */
   /************************************************/
      LOOP
         FETCH CUR_DOCS INTO REC_DOCS;
         EXIT WHEN CUR_DOCS%NOTFOUND;
         HTP.P( '<TR>' ||
               '<TD>' || TO_CHAR(REC_DOCS.ID) || '</TD>' ||
               '<TD>' || CASE REC_DOCS.KBN  WHEN 1 THEN '文' ELSE  '▼' END || '</TD>' ||
               '<TD><A href="doc_show?p_id=' || TO_CHAR(REC_DOCS.ID) || '">' || REC_DOCS.TITLE || '</A></TD>' ||
               '<TD>' || TO_CHAR(REC_DOCS.UPDATE_DATE, 'YYYY/MM/DD HH24:MI' ) || '</TD>' ||
               --  追加した列 2015/08/06 ★★★
               '<TD><INPUT TYPE="CHECKBOX" NAME="P_SEL_ID" VALUE="' ||TO_CHAR(REC_DOCS.ID) || '"></TD>' ||
               '</TR>' );
      END LOOP;
      CLOSE CUR_DOCS;
      HTP.P( '</TABLE>' );
      HTP.P( 'チェックしたディレクトリや文書をディレクトリ番号【<INPUT TYPE="TEXT" SIZE="5" NAME="P_OYA_DIR">】に<INPUT TYPE="SUBMIT" NAME="P_BUTTON" VALUE="移動">します。' );
       HTP.P( '</FORM>' );    -- フォームタグの終わり  2015/08/06  ★★★
       HTP.P( '<script language="JavaScript" type="text/javascript">
<!--
function AllChecked(){
   var check =  document.fname.aaa.checked;
   for (var i=0; i<document.fname.P_SEL_ID.length; i++){
      document.fname.P_SEL_ID[i].checked = check;
   }
}
//-->
</script>' );
  HTP.P( '</BODY>' );
  HTP.P( '</HTML>' );
   END DIRECTORY_DISPLAY;
/*****************************************************************/
/*   実行部                                                      */
/*****************************************************************/
BEGIN
   /********************/
   /* チェック         */
   /********************/
   IF P_ID IS NULL THEN    --番号が指定されていなければ
      DIRECTORY_DISPLAY( NULL );     --最上位のディレクトリ一覧を表示する
   ELSE                    --番号が指定されていれば
       V_ID := TO_NUMBER(P_ID);     --その番号を数字に変換
                           -- その番号でDOCS表の1レコード(行)を取得
       SELECT * INTO REC  FROM DOCS WHERE ID = V_ID;
       /**************************************************************/
       /* その1レコードの区分(KBN)に応じて文書またはディレクトリを表示 */
       /**************************************************************/
       IF REC.KBN = 1 THEN     -- その行が文書(KBN=1)なら
          DOC_DISPLAY(REC);    -- 文書表示のローカルプロシージャをコール
       ELSIF  REC.KBN = 2 THEN -- その行がディレクトリ(KBN=2)なら
          DIRECTORY_DISPLAY(REC);  -- ディレクト表示のローカルプロシージャを
       ELSE
          V_ERRMSG := 'KBN列の値が1,2以外です。ありえません。' ;
          RAISE USER_ERROR;
       END IF;
   END IF;
EXCEPTION
   WHEN VALUE_ERROR THEN
        HTP.P( '文書番号が数値ではありません' );
   WHEN NO_DATA_FOUND THEN
        HTP.P( '指定された番号が文書表(DOCS表)に存在しません' );
   WHEN USER_ERROR THEN
        HTP.P(V_ERRMSG);
   WHEN OTHERS THEN
        V_ERRMSG := SQLERRM;
        HTP.P(V_ERRMSG);
END ;
/
 
プロシージャが作成されました。

このプロシージャは文書を表示するための、ローカルプロシージャ(DOC_DISPLAY 15~31行目)と、ディレクトリを表示するためのローカルプロシージャ(DIRECTORY_DISPLAY 35~99行目)を内部で宣言しています。文書の場合であればDOC_DISPLAYをコールし、ディレクトリであればDIRECTORY_DISPLAYをコールします。(116~123行目)

今回の修正はディレクトリの場合に関係する修正ですので、DIRECTORY_DISPLAYローカルプロシージャ( 35~99行目)の修正となります。今回修正した箇所については「★★★」のマークを付けましたのでこのマークに注目して重要な部分を解説します。

最初に、画面の項目をFORMタグで囲っていることに注目してください(61行目 FORMの開始タグ、86行目 FORMの終了タグ)。今回の修正ではチェックした文書やディレクトリの番号を送信先プロシージャに送信する必要があります。ですからFORMタグが必要となります。送信先は、FORMタグのACTION属性である「doc_dir_man_exe」プロシージャ(61行目)ですが、このプロシージャは次回作成する予定です。

次にチェックボックスの記述です。チェックボックスには2種類ありました。すなわち、全選択・全解除のためのチェックボックスと、個々のディレクトリや文書を選択するためのチェックボックスです。まず個々の文書やディレクトリを選択するためのチェックボックスですが、80行目の記述です。以下にわかりやすく表示します。

<INPUT TYPE="CHECKBOX" NAME="P_SEL_ID" VALUE="文書やディレクトリの番号">

このチェックボックスをチェックして、送信ボタンをクリックすると、P_SEL_IDの名前でその文書やディレクトリの番号がdoc_dir_man_exeプロシージャ(次回実装)に送信されます。複数選択すると、P_SEL_IDの名前で複数の番号が送信されますので、受信側プロシージャでは、P_SEL_IDパラメータをコレクション型で宣言する必要があります。

このP_SEL_IDという名前に関連して62行目をご覧ください。

<INPUT TYPE="HIDDEN" NAME="P_SEL_ID" VALUE="DUMMY">

P_SEL_IDという名前で、画面に表示されない隠し項目(TYPE="HIDDEN")を生成していますが、これはP_SEL_IDチェックボックスがすべて選択されていない場合であっても、最低一つの値を送信する必要があるために記述しています。PL/SQLでは受信パラメータのデフォルト値が定義されていればパラメータそのものが受信できなくてもエラーにはならないのですが、コレクション型のパラメータにはデフォルト値が定義できません。そこで、最低でも一つの値は送信される必要があるので、隠し項目を用意しているわけです。(バックナンバー第112回「WEBアプリ作成(10)(チェックボックスによる複数選択処理 1/2)」参照)

次に、全選択・全解除をするためのチェックボックスです(66行目)。このチェックボックスは"aaa"という名前にしていますが、VALUE属性がありませんので送信先に値が送信されることはありません。したがって、受信側のプロシージャで、aaaという名前のパラメータを設ける必要はありません。このようにこのチェックボックスにはVALUE属性がありませんが、そのかわりonClick属性があります。onClick属性には、AllChecked()というファンクションが指定されています。このチェックボックスをチェックしたり、あるいはチェックを解除するときにこのファンクションが起動します。しかしここでいうファンクションはPL/SQLのファンクションとは全く関係がなく、JavaScriptで定義するファンクションです。その定義はが87~96行目で出力しているscriptタグの中で記述されています。ここで「fname」という名前に注目してください。このfnameは、61行目で記述しているFORMタグの name属性です。いままでFORMそのものに名前を付けるケースはありませんでしたが、JavaScriptで参照するために、FORMに名前をつけました。90行で、fnameのaaaチェックボックスがチェックされているかどうかの状態値をcheckという変数に格納しています。92行目のfor文のループ処理でfname内のすべてのP_SEL_IDチェックボックスのchekedの状態値にcheck変数の値をセットします。これによりすべてのP_SEL_IDチェックボックスがチェックされたり、チェックが解除されたりするわけです。

ではチェックボックスはここまでにして、最後に85行目です。ここでは、移動先ディレクトリ番号(P_OYA_DIR)の項目と、送信ボタンを定義しています。これにより、ボタンをクリックしたときに、移動先ディレクトリ番号も送信されます。

それでは今回はここまでにいたします。次回は、送信先のdoc_dir_man_exeプロシージャを実装いたします。
ご期待ください。

先頭へ戻る