残念ながらPOI4XPagesはDomino9まで。Ver10以降は使えない。 過去の記録ということで。

XPagesでExcel出力

XPagesアプリでExcelファイルを出力したかったらPOI4XPagesがお勧め。

"Apache POI"というWordやExcelファイルを操作するJavaライブラリがあり、それをXPagesから利用しやすくしたのが"POI4XPages"というものらしい。「ポイ for XPages」と呼んでいいようだ。

POI4XPagesを使うには、XPagesサーバとDomino DesignerにPOI4XPagesをインストールする必要がある。 その方法は「POI4XPagesを使ったword-excelへの簡単エクスポート」というスライドがとてもわかりやすい。

最初、POI4XPagesはJavaを覚えないと使いこなせないかと思ってちょっと敬遠してたのだが、実はそんなことなくて、使い慣れたJavaScriptだけで完結できることがわかったので、ここで紹介 してみよう。興味あるのはExcel出力だけなのでWord出力は扱わない。

POI4XPagesのインストールは先ほどのスライドを参考にして済ませておいてください。

まずはサンプル

OpenNTFのPOI4XPagesのページからPOI4XPAGES_1_2_6.zipをダウンロードすると、中にサンプルDBが入ってる。 基本このサンプルを読み解けば、大体の使い方は理解できる。

  • これを開くと、右上に[Administration]というボタンがあるのでこれをクリック
  • [Create Sample Data Set]というボタンがあるのでこれをクリックしてサンプルデータを作っておく

あとは設計を見ながら説明しよう。 Excel出力関係は、"xlsx"で始まるXPageにまとまってる。

xlsx_view_objectlistdatasource.xsp

このXPageはJavaの自作ライブラを呼び出して、ビューから文書の一覧を取り出し、情報の配列を作って、それをExcelに出力するという大変応用の利くサンプルです。 まずはこのサンプルで基本を理解し、次にJavaに頼らずにJavaScriputだけでExcelファイルを出力する方法を説明する。

キモとなる部分を抜き出すとこんな感じ。

  01: <wgpoi:spreadsheet id="shContacts" downloadFileName="allContacts.xlsx">
  02:   <wgpoi:this.templateSource>
  03:     <wgpoi:resourcetemplate fileName="SampleExcel.xlsx" />
  04:   </wgpoi:this.templateSource>
  05:   <wgpoi:this.spreadsheets>
  06:     <wgpoi:table name="Contacts" create="false">
  07:       <wgpoi:this.cellValues>
  08:         <wgpoi:cellBookmark name="user">
  09:           <wgpoi:this.value><![CDATA[#{javascript:@Name("[CN]",@UserName())}]]></wgpoi:this.value>
  10:         </wgpoi:cellBookmark>
  11:         <wgpoi:cellBookmark name="date" value="#{javascript:@Text(@Today())}" />
  12:       </wgpoi:this.cellValues>
  13:       <wgpoi:this.exportDefinitions>
  14:         <wgpoi:data2rowExport startRow="4" stepSize="1" var="row" index="index">
  15:           <wgpoi:this.dataSource>
  16:             <wgpoi:ListObjectDataSource buildValues="#{javascript:
  17:               contactBean.getAllContacts()
  18:             }"  />
  19:           </wgpoi:this.dataSource>
  20:           <wgpoi:this.columns>
  21:             <wgpoi:columnDefinition columnNumber="0" columnTitle="FirstName" rowShift="0" />
  22:             <wgpoi:columnDefinition columnNumber="1" columnTitle="LastName" rowShift="0" />
  23:             <wgpoi:columnDefinition columnNumber="2" columnTitle="City" rowShift="0" />
  24:             <wgpoi:columnDefinition columnNumber="3" columnTitle="State" rowShift="0" />
  25:             <wgpoi:columnDefinition columnNumber="4" columnTitle="EMail" rowShift="0" />
  26:             <wgpoi:columnDefinition columnNumber="5" computeValue="#{javascript:index}" />
  27:             <wgpoi:columnDefinition columnNumber="6" computeValue="#{javascript:
  28:                  row.getDocument().getUniversalID()
  29:             }" />
  30:           </wgpoi:this.columns>
  31:         </wgpoi:data2rowExport>
  32:       </wgpoi:this.exportDefinitions>
  33:     </wgpoi:table>
  34:   </wgpoi:this.spreadsheets>
  35: </wgpoi:spreadsheet>
  36:
  37: <xp:button value="get Excel-File" id="btExport">
  38:   <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
  39:     <xp:this.action>
  40:       <wgpoi:generateWorkbook workbookId="shContacts" />
  41:     </xp:this.action>
  42:   </xp:eventHandler>
  43: </xp:button>
  
  • 01〜35行目でExcelシートに出力する形式を定義してる。01行目の<wgpoi:spreadsheet>のidを、40行目のファイル出力処理で指定することで、どのようにExcel出力したいかいろいろ指定できる。 downloadFileName属性で出力ファイル名を指定してる。downloadFileName="#{javascript:viewScope.fname}"のような指定もできる。
  • 02〜04行目でテンプレートファイルを指定。テンプレートExcelファイルは「リソース/ファイル」内に組み込んでおく
  • 06行目でどのExcelワークシートに書き出すか指定。テンプレートExcelブックのワークシートを指定する
  • 07〜12は特定のセルに計算で求めた文字列を書き出すことができる。Excelのセルに"<<tag>>"のように書いておけば、その部分を指定文字列に置き換えることができる
  • 13〜32でExcel上の1行分の記述方法を決めている。
  • 15〜19行目がデータソースとなる配列を求めてる。contactBean.getAllContacts()がそれ。contactBeanはContactBean.javaから作られるオブジェクト。この中にgetAllContacts()というメソッドが 定義されてて、文書の一覧から求めた情報の配列を返してくれる。配列の各要素は14行目で指定されてる"row"という変数名でアクセスできる。getAllContacts()メソッドのコードを読むとわかるのだが これはContactクラスのオブジェクト。こいつはContact.javaで定義されたアクセサメソッドが定義されてる。getFirstName()とかgetCity()とか。
  • 21〜29でExcelの各列に入れる文字列を求めてる。columnTitleで指定してる名前がプロパティになってて、columnTitle="FirstName"だとrow.getFirstName()というメソッドが呼び出されて文字列が得られる
  • どのセルに書き出すかはcolumnNumberで指定する。 columnNumber="6"だと6列目に書き出すという意味になる。rowShiftは行方向のオフセット。1行づつ書き出すならrowShift="0"で固定なんだけど、 複数行をまとめて書くときは(14行目のstepSizeで指定)、1つのカタマリの何行目に書くか指定できる。

とまぁこんな感じでExcelの出力を制御できる。 このサンプルではデータソースをJavaのオブジェクトから得てるし、rowもJavaのオブジェクトだしで、Javaのコードを書かないといけない。

JavaScript用に改造

POI4XPagesはデータソース定義部でJavaのオブジェクトだけでなく、JavaScriptのオブジェクトも扱うことができる。 先ほどのサンプルの一部を以下のように改造するとJavaScriptのオブジェクトを得られる。

  01:   <wgpoi:data2rowExport startRow="4"
  02:       stepSize="1" var="row" index="index">
  03:     <wgpoi:this.dataSource>
  04:       <wgpoi:ListObjectDataSource
  05:         buildValues="#{javascript:$poi.getDataList()}">
  06:       </wgpoi:ListObjectDataSource>
  07:     </wgpoi:this.dataSource>
  08:     <wgpoi:this.columns>
  09:       <wgpoi:columnDefinition columnNumber="0"
  10:         computeValue="#{javascript:row.FirstName}" rowShift="0" />
  11:       <wgpoi:columnDefinition columnNumber="1"
  12:         computeValue="#{javascript:row.LastName}" rowShift="0" />
  13:       <wgpoi:columnDefinition columnNumber="2"
  14:         computeValue="#{javascript:row.City}" rowShift="0" />
  15:       <wgpoi:columnDefinition columnNumber="3"
  16:         computeValue="#{javascript:row.State}" rowShift="0" />
  17:       <wgpoi:columnDefinition columnNumber="4"
  18:         computeValue="#{javascript:row.EMail}" rowShift="0" />
  19:       <wgpoi:columnDefinition columnNumber="5"
  20:         computeValue="#{javascript:index}" />
  21:       <wgpoi:columnDefinition columnNumber="6"
  22:         computeValue="#{javascript:row.UNID}" />
  23:     </wgpoi:this.columns>
  24:   </wgpoi:data2rowExport>
  
  • 05行目で$poi.getDataList()というメソッドが返す配列をデータソースにしている
  • 10行目などでrow.FirstNameなどが返す文字列をExcelのセルに書き込んでる

$poi.getDataList()はこんな感じで定義されてる。

  01:   $poi.getDataList = function() {
  02:     var all_view = database.getView("AllContacts");
  03:     var doc = all_view.getFirstDocument();
  04:     var list = [];
  05:     while(doc) {
  06:       var item = {};
  07:       item.FirstName = doc.getItemValueString("FirstName");
  08:       item.LastName  = doc.getItemValueString("LastName");
  09:       item.City      = doc.getItemValueString("City");
  10:       item.EMail     = doc.getItemValueString("EMail");
  11:       item.State     = doc.getItemValueString("State");
  12:       item.UNID      = doc.getUniversalID();
  13:       list.push(item);
  14:       var next_doc = all_view.getNextDocument(doc);
  15:       doc.recycle()
  16:       var doc = next_doc;
  17:     }
  18:     return list;
  19:   }
  

このサンプルでは、ビューの全文書をループでまわしながら必要なフィールド値を抜き出しながらlistに詰め込んでいる。 必要に応じて選別もできるが文書数が多いときは注意が必要。

Excel出力ボタンの連続押下問題

サンプルではExcel出力ボタンを押下すると、しばらくボタンの再押下ができない。 ブラウザからの通信をチェックしてみるとボタン押下でPOST要求を出したはいいけど、サーバから応答が来ないため、応答タイムアウトがおきるまで次のPOST要求が出せない。

応答が返せればいいけどそこで、POSTの応答を自由に返すのは難しい。そこでCSJSでページ遷移させてしまえばよさそう。 こんなコードをボタン押下のCSJSのイベントにいれておけばいい。

  setTimeout("location.reload()",3000);