XPagesでダイアログボックス

XPagesでダイアログボックスを出すサンプルを探していて、いいものを見つけたので紹介する。

XPages技術者コミュニティーのダイアログボックスのサンプルDB(8.5.3 Upgrede Pack 1 使用) というサンプルだ。結構古いので読んだことはあるとは思うのだが。

ボタンを押すと、ダイアログボックスがふわっと表示され、そこで選択した人の名前がボタン横のテキストボックスに入力されるというもの。 ダイアログボックスのサンプル

このサンプルで使われているテクニックはこんな感じ。

  • カスタムコントロールをダイアログボックスとして使う方法
  • ダイアログボックスの表示/非表示を制御する方法
  • ダイアログボックスで選択した情報をメインのページに引き渡す方法
  • クライアントスクリプトのライブラリを定義
  • DojoのDialogの使い方

あと、ダイアログボックスとは直接は関係ないが、以下のテクニックも参考になる。

  • ビューにチェックボックスを付けて、選択した対象の情報を得る

※ これまでずっとSSJS(Server Side JavaScript)一筋だったが、いよいよCCJSにも足を踏み入れるときが来たか...

サンプルアプリケーションの構造

サンプルとはかくありたい、という実にシンプルな構成だ。

  • メインページのXPage(xpHome.xsp)
  • ダイアログボックスのカスタムコントロール(ccPicklist.xsp)
  • ダイアログボックスを組み込むクライアントサイドJavaScript(libDialog.js)

サンプルコードの解析

xpHome.xsp

メインのページを作っている。

重要なのは、以下の2点。

  • ページにダイアログボックスを組み込んでおく方法
  • ボタンが押されたときにダイアログボックスに制御を移す方法

導入部

では、コードを読んでいこう。XPagesのソースの読み方で紹介しているコードの整形ツールの結果で説明する。

  01: <?xml version='1.0' encoding='UTF-8'?>
  02: <xp:view xmlns:xc="http://www.ibm.com/xsp/custom" dojoParseOnLoad="true" xmlns:xp="http://www.ibm.com/xsp/core" dojoTheme="true">
  03:     <xp:this.resources>
  04:         <xp:script src="/libDialog.js" clientSide="true" />
  05:     </xp:this.resources>
  06:     <script language="Javascript">
  08:               dojo.require("dijit.Dialog");
  09:               XSP.addOnLoad(function(){dialog_create("ccPicklist","Address Book")});
  11:     </script>
  12:     <xp:this.data>
  13:         <xp:dominoDocument var="dominoDocument1" formName="fmMain" />
  14:     </xp:this.data>
  
  • 2行目では通常のXPageでは出てこないDojo関係のおまじないがいくつか載っている。意味はわからなくても、DojoのDialogを使うときにはこうすると思っておけばいいだろう
  • 4行目で読み込んでいるのは自作のJavaScriptのライブラリ。clientSide=trueになってるので、クライアントサイドスクリプト(CCJS)であることがわかる
  • 6〜11でdialog_create()という関数をページが表示されたときに実行されるように仕込んでいる。dialog_create()は4行目で読み込んだlibDialog.jsに定義されている関数。 第1引数がダイアログボックス用の領域のid. この後出てくる。第2引数がダイアログのタイトル。
  • 13行目は普通のデータソースの定義。dominoDocument1をfrMailフォームの文書に紐付けてる

ダイアログを開くボタン([Address Book]ボタン)

ダイアログを開くボタンである[Address Book]のコードを見てみよう。

  20:  <xp:button id="btnPicklist" value="Address Book">
  21:      <xp:eventHandler submit="false" event="onclick">
  22:          <xp:this.script>
  23:            <![CDATA[
  24:              dijit.byId('ccPicklist').show()
  25:            ]]>
  26:          </xp:this.script>
  27:      </xp:eventHandler>
  28:  </xp:button>
  30:  <xp:td style="width:480px">
  31:      <xp:inputTextarea id="sendTo1" value="#{dominoDocument1.SendTo}" 
              multipleTrim="true" style="height:40px;width:99%" multipleSeparator="," />
  32:  </xp:td>
  
  • 21行目でイベントハンドラを定義してるが、submit=falseになっているのでCSJSであることがわかる。要はブラウザ内で閉じた処理。 やってるのはdijit.byId('ccPicklist')で取ってきた要素(ダイアログ)を表示させているだけ。
  • ダイアログを開く前処理としてのSSJSの処理を組みたいなら、21行目をsubmit="true" refreshMode="partial" refreshId="xxxx"のように書く
  • 31行目はダイアログボックスで選択したデータを格納する箱

ダイアログを表示する箱の用意

ダイアログを置いておく場所を用意。

  53: <div id="ccPicklist" style="display:none">
  54:    <xc:ccPicklist />
  55: </div>
  
  • 53行目はダイアログを置いておく場所を用意してる。id="ccPickelist"は導入部の9行目で指定してたIDのこと。
  • 54行目はダイアログパネルのデザインを決めているカスタムコントロール。カスタムコントロールの名前はccPickerlistでなくてもかまわない

ccPicklist.xsp

ダイアログボックスの設計を決めているカスタムコントロールである。

ダイアログボックス

パネル全体

まずはパネル全体。ごく普通のカスタムコントロール。

  01: <?xml version='1.0' encoding='UTF-8'?>
  02: <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
  03:     <xp:panel style="width:99.0%">
  途中省略...
  57:     </xp:panel>
  58: </xp:view>
  
  • 3行目のwidth:99%は、ダイアログパネル全体に対しての大きさ。ダイアログパネル自体の大きさはここでは指定できない

OKボタン

OKボタンが押されたら、ビューで選択された名前を取り出し、データソースのSendToを書き換える。

  04: <xp:button id="btnSelect" value="OK" style="width:70px">
  05:     <xp:eventHandler refreshId="sendTo1" refreshMode="partial" submit="true" event="onclick">
  06:         <xp:this.action>
  07:           <![CDATA[
  08:             #{javascript:
  09:               var viewPanel:com.ibm.xsp.component.xp.XspViewPanel = getComponent("viewPanel1");
  10:               var docIDArray = viewPanel.getSelectedIds();  
  11:               var pickData = new Array();
  12:               for( i=0 ; i < docIDArray.length ; i++ ){
  13:                 var docId = docIDArray[i];
  14:                 var doc = database.getDocumentByID(docId);
  15:                 //get value
  16:                 if(doc != null){
  17:                   pickData[i] = doc.getItemValueString("MailAddress")
  18:                 }
  19:               }
  20:               if(pickData != null){
  21:                 //replace value
  22:                 dominoDocument1.replaceItemValue("SendTo", pickData)
  23:               }
  24:             }
  25:           ]]>
  26:         </xp:this.action>
  27:         <xp:this.script>
  28:           <![CDATA[
  29:             dijit.byId('ccPicklist').hide()
  30:           ]]>
  31:         </xp:this.script>
  32:     </xp:eventHandler>
  33: </xp:button>
  
  • 5行目でイベントハンドラを定義。部分更新を指定しないと、ダイアログボックスがふわっと消えないのでそれらしくない
  • ダイアログを表示するボタンと違ってこちらはsubmit="true"で部分更新をかけているので閉じる時の前処置としてのSSJSも記述できる
  • 6〜26は、ボタンの1つめの処理。選択された名前を取り出して22行目でデータソースのSendToに書き込んでいる。ここはまぁ普通の選択文書に対する処理
  • 27〜31は2つめの処理。自分を隠してる。CCJS
  • 隠してるだけなので、再度ダイアログを開くと、選択状態などは保持されてる。ここに選択状態をクリアする処理などをいれておくといい。

キャンセルボタン

  34: <xp:button id="button1" value="Cancel" style="width:70px">
  35:     <xp:eventHandler submit="false" event="onclick">
  36:         <xp:this.script>
  37:           <![CDATA[
  38:             dijit.byId('ccPicklist').hide()
  39:           ]]>
  40:         </xp:this.script>
  41:     </xp:eventHandler>
  42: </xp:button>
  
  • ダイアログをを隠してるだけ。そのため、35行目ではsubmit=falseにして、サーバ側への通信はしないようにしている
  • ここも隠しているだけなので、選択状態は維持されている。選択状態をクリアする処理をSSJSで行うなら、35行目のsubmitはtrueにしないといけないだろう

ビューパネル

  43: <xp:viewPanel viewStyle="width:99%" id="viewPanel1" rows="30">
  44:     <xp:this.facets>
  45:         <xp:pager id="pager1" partialRefresh="true" xp:key="headerPager" layout="Previous Group Next" />
  46:     </xp:this.facets>
  47:     <xp:this.data>
  48:         <xp:dominoView var="view1" viewName="vwUser" />
  49:     </xp:this.data>
  50:     <xp:viewColumn showCheckbox="true" columnName="FullName" id="viewColumn1">
  51:         <xp:viewColumnHeader id="viewColumnHeader1" value="Name" />
  52:     </xp:viewColumn>
  53:     <xp:viewColumn id="viewColumn2" columnName="$2">
  54:         <xp:viewColumnHeader id="viewColumnHeader2" value="Mail  address" />
  55:     </xp:viewColumn>
  56: </xp:viewPanel>
  
  • 名前の一覧をだしているところ

libDialog.js

ダイアログを設定する関数。

  06: function dialog_create(id, title1) {
  07:     var dialogWidget = dijit.byId(id);
  08:     if( dialogWidget ) {
  09:       dialogWidget.destroyRecursive(true);
          }
  10:     dialogWidget = new dijit.Dialog({ title: title1, duration:500},dojo.byId(id));
  11:     
  12:     var dialog = dojo.byId(id);
  13:     dialog.parentNode.removeChild(dialog);
  14:     var form = document.forms[0];
  15:     form.appendChild(dialog);
  16:     
  17:     dialogWidget.startup();
  18: }
  
  • まぁここは丸々パクリで。意味はまだ正確にはわからない
  • 10行目のduration:500はふわっと表示されるレート。おおきいほどゆっくり出てくる
  • パネルの大きさはこのサンプルでは指定してない。大きさを指定するなら、10行目でdijit.Dialog()の第1引数にstyle:"width:500px"を追加すればいい

ダイアログを開くときにSSJSで前処理

今回のサンプルでは、ダイアログを開く前処理を記述してない。ここにSSJSで前処理を記述できればもっと使い道が出てくる。 そのためには、以下のように改造すればいい。

  <xp:button id="btnPicklist" value="Address Book">
      <xp:eventHandler submit="true" event="onclick" refreshMode="partial" refreshId="innerDialog">
          <xp:this.action><![CDATA[#{javascript:
            // ここにSSJSの前処理を記述可能
          }]]></xp:this.action>
          <xp:this.script>
            <![CDATA[
              dijit.byId('ccPicklist').show()
            ]]>
          </xp:this.script>
      </xp:eventHandler>
  </xp:button>
  
  • <xp:eventHandler>の部分を部分更新の形に書き直す。更新先はダイアログボックスの内側
  • <xp:this.action>に前処理として実行させたいSSJSを記述しておく

これだけで前処理が実装可能になる。

予想だけど、内部ではこんな順序で処理されるんだと思う。

  • クライアントサイドでdijit.byId('ccPicklist').show()の処理が実行され、非表示だった<div id="ccPicklist">が表示される
  • SSJSの前処理をするためにsubmit="true"にして、ボタンが押されたときに、サーバ側に処理を依頼
  • <this.action>に書かれた部分がサーバ側で実行
  • 実行完了後、refreshIdで指定された要素に相当する箇所を再計算して、その部分のHTMLをブラウザに落とし込み、ダイアログの内部が書き換わる

ふーん、よくできてるなぁ。

そのため、ダイアログボックス内を<xp:panel id="innerDialog">で囲っておいてやる。ここで指定している"innerDialog"はダイアログの表示ボタンの 部分更新先に指定したIDと一致させる必要がある。