「XPagesの活用」で紹介されている方法の応用

XPagesの活用」という記事があるのだが、ここで紹介されている方法は、XPagesを理解するのにとてもいい題材のような気がする。 応用もいろいろ利きそう。ということで、自分なりにまとめてみようと思う。

目次

画面上で次々に文書を保存していくサンプル

「XPagesの活用」の最初の方に、編集フィールドが並んでて、[保存]ボタンを押すたびに、文書が作成されていくサンプルが載っていた。

XPagesの活用のサンプル

このサンプルをベースにして、いろいろ改造しながらXPagesについての理解を進めて行ってみる。

ここで紹介したサンプルは、xpageslecture.nsfからDLできる(応用9まで)。応用1で紹介したものはmain_1というXPageという対応で。

基本となるアプリケーションの作り方

まずは、「アプリケーション」という入れ物を作らないといけない。

XPagesアプリの新規作成

エラー表示の設定

これで入れ物の準備は完了。

XPageの作成

mainという名前のXPageを新規作成する。

そこに編集ボックスを2つほど貼り付けて、[プロパティ/データ/使用するバインドデータ]を以下のように設定しておく。

requestScopeへのバインド

これで、[保存]を押すたびに、文書が作成されていく。 クラシカルNotesのビューも作成しておき、文書が生成され、フィールドも正しく設定されていることを確認しておこう。

このサンプルのポイントは、

数値ボックスのエラー

応用その1:日付を設定してみる

では、最初の応用として、日付を入力できる編集ボックスを用意して、日付を文書に保存してみよう。

日付フィールドの追加

  var doc = database.createDocument();
  doc.replaceItemValue("subject", requestScope.subject);
  doc.replaceItemValue("number", requestScope.number);
  doc.replaceItemValue("date", requestScope.date);
  doc.replaceItemValue("form", "main");
  doc.save();
  requestScope.subject = null;
  requestScope.number = null;
  requestScope.date = null;
  

すると、以下のようなエラーメッセージが表示された。

  JavaScript アクション式を実行中にエラーが発生しました
  スクリプトインタープリタエラー、行=4、列=5: [TypeError] メソッド 
  NotesDocument.replaceItemValue(string, java.util.Date) を呼び出し中に例外が発生しました null
  

エラーが起きているのは、doc.replaceItemValue("date", requestScope.date);の部分。 これは、NotesDocument.replaceItemValue()の第2引数のrequestScope.dateがjava.util.Dateだから、受け付けられない、という意味だろう。 要は、Java形式のDateクラスじゃなくて、Notes形式のNotesDateTimeクラスをインスタンスを指定する必要があるようだ。

java.util.DateクラスにNotesDateTimeクラスへの変換メソッドがあると便利なのだが、どうもそういうのはなさげ。 java.util.Dateクラスのヘルプは、 Domino Information Centerを開き、「Lotus Domino Designer 8.5」→「Lotus Domino Designer XPageのリファレンス」→ 「Standard」→「Date」と進んでいくと、java.util.Dateクラスのメソッドやプロパティの一覧を調べることができる。

日付を示す文字列が手に入れば、session.createDateTime()でNotesDateTimeが手に入るので以下の方法がよさそう。ただちょっと面倒だ。

  var date_val:Date = requestScope.date;
  var date_str = date_val.getFullYear() + "/" + (date_val.getMonth() + 1) + "/" + date_val.getDate();
  var date_notes = session.createDateTime(date_str );
  

ヘルプを読んで気づいたのだが、sessionのcreateDateTime()ならjava.util.Dateを受け付けてくれそう。

  var date_val:Date = requestScope.date;
  var date_notes = session.createDateTime(date_val );
  

ということで、こんな感じで日付データを変換してやればいい。

  var doc = database.createDocument();
  doc.replaceItemValue("subject", requestScope.subject);
  doc.replaceItemValue("number", requestScope.number);
  var date_val:Date = requestScope.date;
  var date_notes = session.createDateTime(date_val);  // こっちの方が簡単だね
  doc.replaceItemValue("date", date_notes);
  doc.replaceItemValue("form", "main");
  doc.save();
  requestScope.subject = null;
  requestScope.number = null;
  requestScope.date = null;
  

応用その2: 文書タイプのデータソースを利用してみる

先ほどのNotesDocumentのreplaceItemValue()を使う方法は、文書保存時にいろいろ前処理を行えるので、便利そうではある。ただ、フィールドが増えてくると、手に負えなくなる。 そういう場合は、やっぱり、文書タイプのデータソースを使った方がいいだろう。

mainフォーム

データパレット

データソースの定義

データソースの貼り付け

このように、文書タイプのデータソースを貼り付けておくと、シンプルアクションで文書の保存を行うだけで、文書を作成できる。ただ、この方法では、anonymousの権限を「編集者」 以上にしておく必要がある。作成者では、「作成者の権限がないので文書の更新/削除はできません。」というエラーが出てしまう。

また、この方法だと、文書を保存しても、編集ボックスの中身がクリアされない。シンプルアクションで「文書の保存」をしただけだからしょうがない。

応用その3: 文書の保存前に前処理をする

文書を保存する前に、前処理を追加してみる。 ここでは、編集ボックスの値をちょっと変更してみる。ここでは単純に、numberの値を+100してから保存してみよう。 同時に、保存したら編集ボックスの中身はいったんクリアしておこう。

  document1.number += 100;
  document1.save();
  

この方法では、+100されることはなかった。どうもrequestScope.numberとはこの辺が本質的に違うようだ。

  document1.ujauja();
  
  JavaScript アクション式を実行中にエラーが発生しました
  スクリプトインタープリタエラー、行=1、列=11: [TypeError] タイプ「NotesXspDocument [Static Java Wrapper, com.ibm.xsp.model.domino.wrapped.DominoDocument]」の
  オブジェクトでメソッド「ujauja()」を呼び出し中にエラーが発生しました
  
  var current_number = document1.getItemValue("number");
  document1.setValue("number", current_number+100);
  document1.save();
  

これを実行すると、[number]フィールドには、"[50.0]100" という値がセットされる。

クラシカルNotesでLotusScriptに慣れている人は、NotesDocumentのgetItemValue()は配列を返してたことに気づくかも。

実際、current_numberは[50]になっている。[50] + 100 を、JavaScriptは"[50.0]100"と計算するようだ。変なやつ。

ということで、正解はこう。

  var current_number = document1.getItemValue("number")[0];  // 今は値があるかどうかのチェックは省略
  document1.setValue("number", current_number+100);
  document1.save();
  

値のクリアを入れるなら、こうなる。

  var current_number = document1.getItemValue("number")[0];  // 今は値があるかどうかのチェックは省略
  document1.setValue("number", current_number+100);
  document1.save();
  document1.setValue("subject", "");
  document1.setValue("date", "");
  document1.setValue("number", "");
  

一見、これでちゃんと動いているように見えるのだが、クラシカルNotesのビューで確認するとちょっとへんなことに気づく。

  var current_number = document1.getItemValue("number")[0];  // 今は値があるかどうかのチェックは省略
  document1.setValue("number", current_number+100);
  document1.save();
  context.redirectToPage("main_3.xsp")
  

応用その4: 作成者フィールド/読者名フィールド

XPageから文書を保存したときに、作成者フィールド、読者名フィールドを設定したいこともある。 たとえば、[readers]というフィールドに現在のユーザー名をセットするには、NotesItemクラスのsetReaders(true)メソッドを使えばいい。

最初のdoc.replaceItemValue("date", date_notes); を使うタイプなら、こんな感じでできるだろう。

  var current_user = session.getUserName();
  doc.replaceItemValue("reader", current_user);
  var item = doc.getFirstItem("reader");
  item.setReaders(true);
  doc.save();
  

Notesクライアントを起動し、文書の一覧を表示するビューで該当の文書のプロパティを出して、readerフィールドが読者名フィールドになっていることを確認してみよう。

読者名フィールドの確認

データソースを使う方式の場合は、一工夫必要になる。

フィールドのクリアは省くけど、こんな感じのコードになる。

  var current_number = document1.getItemValue("number")[0];  // 今は値があるかどうかのチェックは省略
  document1.setValue("number", current_number+100);
  
  // 読者名フィールドを設定
  var current_user = session.getUserName();
  document1.setValue("reader", current_user);
  var doc = document1.getDocument(true);
  var item = doc.getFirstItem("reader");
  item.setReaders(true);
  document1.save();
  context.redirectToPage("main_4.xsp")
  

これでちゃんと[reader]が読者名フィールドになる。

もちろん、作成者フィールドを設定するなら、setAuthors(true)を使えばいい。

応用その5: ビューを用意

「XPagesの活用」では、この後、「データ表」を使って、文書の一覧を作っていた。かわりに、「ビューコントロール」を使ってみよう。

ビューの作成

が、ここで一度はまった。

先ほど、読者名フィールドを設定するサンプルを説明したが、この制限が働いて、ビューコントロールに文書が表示されないのだ。

ということで、読者名フィールドを設定する機能は削っておこう。 こうすれば、ビューコントロールに入力した文字が表示されるようになる。

ビューには文書の一覧が表示されるわけだが、ビューのある行をクリックしたら、対応する文書のフィールドが入力欄に反映されるようにしたい。 当然、入力欄で変更を行い、[保存]を押したら、今度は新規文書作成ではなく、変更が文書に反映されるようにしたい。

まずは、ビューコントロール自体の設定。以下のように設定し、「実行したらmain_5.xspを開く」と設定しておく。

ビューコントロールの設定

さらに、どれかの列を「リンクとして表示」に設定しておく。

列の設定

これで、リンクとしての列をクリック→ビューコントロール実行→該当する文書を編集モードで開く みたいに、処理が流れるようだ。 これでビューをクリックして、内容を変更し、[保存]を押せば、文書が変更されるようになった。

サンプル

応用その6: ビューに削除用チェックボックスをつける

ビューをちょっと拡張して、チェックボックスを表示させて、選択した文書をまとめて削除できるようにしてみよう。 こんな感じ。

ビューのチェックボックス

チェックボックスの設定

チェックボックスの設定

ただ、これでは削除専用の仕組みなので、ちゃんとスクリプトでかけないと応用がきかない。たとえば、選択された文書だけ、 まとめて「処理済み」のマークをつけてみるとか。

「選択されたもの」を取得する仕組みがあるはずだ。あるとしたら、ビューコントロールだろう。ビューコントロールはXspViewPanelクラス なので、このクラスのメソッドを調べてみる。が、Domino Information Center には、「XspViewPanelクラス」のことは載ってない。

Googleで検索すると、以下のページにXPagesの各クラスの説明が載っていた。

  var panel = getComponent("viewPanel1");
  var id_arr = panel.getSelectedIds();
  for(var docid in id_arr) {
    var doc = database.getDocumentByID(docid);
    doc.remove(true);
  }
  

うーむ、たいしたものだなぁ。 その後、以下のページでも紹介されているのを見つけた。

応用その7: サブ項目のあるXPage

1つの申請書に複数の項目を入力するタイプのXPageを作ってみる。

サブ項目のあるXPage

これはちょっと記述内容が多いので、応用7_サブ項目のあるXPageにまとめた。

応用その8: サブ項目を保存してみる

応用7ではサブ項目をsessionScope.item_listに保存することができるようになった。 これを文書に保存することを考えてみよう。

これも記述内容が多いので、応用8_サブ項目を保存してみるにまとめた。

応用その9: 妥当性検査

入力すべき項目が未入力のままだったり、関連する情報を示すIDが存在しない場合など、エラーではじく必要がある。こういうのを妥当性検査 (validator)というらしい。XPagesにも妥当性検査を行う機能があるので試してみる。

たとえば、Subjectが空のまま保存しようとしたときに、エラーではじくようにしてみる。

Subjectを入れる編集ボックスのプロパティの「妥当性検査」のタブを見ると、「必須フィールド」というのがある。これがそれっぽい。 これにチェックを入れて、エラーメッセージを入れておけば、未入力のまま保存しようとすれなエラーが表示されるはずだ。

妥当性検査

やってみたら、確かにエラーが表示される。ただ、初めて画面を開くときにもなぜかエラーが表示されてしまう。 必須項目のエラー

保存処理の中で各入力項目の有無や入力内容をチェックして、エラー表示する方法の方がよかったりするような気もする。

エラーチェック処理

文書を保存するときの[保存]ボタンに、以下のコードを入れてみる。subject項目が空だったら、sessionScope.subject_err をtrueに、 問題なければfalseにセットする。

  var subject = document1.getItemValue("subject");
  if (subject[0] == "") {
    sessionScope.debug = "subject は必須項目です";
    sessionScope.subject_err = true;
    return;
  } else {
    sessionScope.subject_err = false;
  }
  // この後ろに文書の保存処理が続く
  ...
  ...
  

今はsessionScope.debugを表示する編集ボックスを用意してたのでそこに表示させてみたが、本来は専用の表示エリアを設けるべきだろう。 また、チェック項目を複数用意して、複数のエラーを設定することもできる。

後は、sessionScope.subject_errの内容に応じて編集ボックス付近の表示を工夫すると、それっぽい。

必須項目のエラー表示

これが正解なのかよくわからないが、正常系のラベルと、異常系のラベルを並べて、それぞれの表示/非表示を先ほどセットした sessionScope.subject_errの値に応じて切り替えてみればいいのだろうか。

異常ラベルの表示切替

応用その10: リッチテキスト

リッチテキストコントロールを試してみる。 これを使うと、ブラウザから入力するときに、フォントの指定やイメージの貼り付け、表の作成など、「ブラウザでここまでできるの?」と驚く ようなことができる。

リッチテキストコントロール

クリップボードから直接イメージを貼り付けることができるかどうかは、ブラウザの種類によるような気がする。Firefox5.0にアップデートして からできるようになった気がする。

これだけで保存した文書の表示もできる。

firebugなどで編集中のページを見ていると、入力した時点ではプレーンテキストだが、画面上部のコントロールなどを使って、フォントの指定などを するたびに、HTMLを生成しているみたい。JavaScriptってこういうことまでできるんだなぁ、とあらためて感心。

どうやって保存されている?

Notesクライアントで[body]フィールドを見てみると、htmlが入っていた。貼り付けたイメージなどはbase64で変換されて文字列として直接参照されていた。

  <img alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAakAAAD5CAIAAAASt11