XPagesアプリ設計の基本型

XPagesアプリを作り始めるときに、アプリの作り方の型があるとうれしかったような気がする。

我流だけど、自分のXPagesアプリの作り方の流れみたいなものを紹介してみよう。

DB

最初にXPageアプリの入れ物になるDBファイルを作成することになる。 DBファイル名がそのままXPageアプリの名前になるので、短くてわかりやすい名前にしておこう。

大文字、小文字の違いはXPagesサーバが吸収してくれるが、全部小文字が基本。

業務で使うXPagesアプリならテンプレートにすべきだけど、設計中はNSFファイルのままにしておくほうが楽。テンプレートにしてしまうと、 テスト実行するときにいちいち実行可能なNSFファイルに設計を反映しないといけないので、ちょっと手間。

DBの設定

箱ができたら、DB(アプリケーション)のプロパティで必要な設定をしておく。

  • DBを選択し、アプリケーションのプロパティを表示
  • [XPages]タブの"xsp.properties"に移動
  • [一般]タブでは、"アプリケーションのテーマ"に"OneUI V2"を選択
  • "XPage実行時エラーページを表示"にチェックをいれておく

フォームの設計

  • [永続性]タブでは、"サーバページの永続性"を"ページをメモリに保持する"に設定
  • XPageライブラリでは、Extension Libraryにチェックをいれておく

フォームの設計

永続性を「メモリに保持」にしてるのは、JavaScriptで自作のクラスを使うため。自作のクラスを使うときには、ページ情報をオンメモリにしておかないとエラーになってしまう。 たぶん、Dominoのバグというか制限事項。

フォームの作成

XPagesの場合、データのかたまりを「文書」内の「フィールド」に格納する。 文書にどんなフィールドがあるかは、「フォーム」で定義する。

フォームとフィールドの名前

というわけで、まずはフォームの設計を行う。フォームの名前には、保存される文書をどう呼ぶのがしっくりくるかを基準に考えればいい。

例えば、人事情報を扱うXPagesアプリケーションでは、各社員毎に作成される文書を「社員文書」と呼ぶなら、そのフォーム名は「社員」にしておけばいい。 会社の情報を保存するフォームなら「会社」のように。

なお、XPagesのコードでは、フォーム名は文書を新規に作成するときくらいにしか使わないので、多少わかりにくい名前でも何とかなる。

各データ(氏名、所属、生年月日など)はフィールドに格納されるので、大事なのはフィールドの名前。

XPagesのコードでデータを読み出すときには、フィールド名を指定して読み出すので、格納されているデータが何なのか連想しやすいフィールド名にすることが必要。 日本でしか使わないアプリなら、フィールド名を漢字で表記するのもアリだと思う。

XPagesの場合、フィールドの配置は、XPagesの画面上での見栄えとは関係ないので、適当に配置していけばいい。 ただ、アプリケーションのデータ保守のために、Notesクライアントで文書を開くことはあるので、配置の工夫と注釈はちゃんとつけておく。

フォームの設計

フィールドの種類

フィールドにはいろんな種類があるが、XPagesで使うのは以下の6種類。

  • テキスト : もっとも一般的。数値や文字列形式の数値や日付を格納することもある。JSON形式の構造化データを格納するのにも使う
  • 数値: 数値データを格納するときに使う。ただ、テキストフィールドでも、xsp_doc.getItemValueIntegerなんてのもあるので、テキストフィールドに数値を入れるのも可能なのでまぁ使わなくてもOK
  • 日付/時刻: 表示用の日付くらいだったら、テキストでOK。有効期限のように、読み出した日付から何日経過しているか、といった時間の演算を行う場合には日付/時刻形式のフィールドを使う。
  • リッチテキスト: 添付ファイルやデータ量が大きくなるJSONデータなどを格納するときはこれを使う。
  • 作成者: 編集権限を制限したいときに使う。ユーザー名、グループ名、ロール名が指定できる
  • 読者: 文書を読める人を制限するときに使う。ユーザー名、グループ名、ロール名が指定できる。

フィールドの種類がなんであっても、XPagesのコードによってどの形式で保存されるか決まるのだが、Notesクライアントで文書を変更したときに、この設定が効くので、けっこう大事。

テキストフィールドには32KBの壁があるので、大きめのJSONデータを格納するなら、最初からリッチテキストにしておこう。

フィールドの複数値設定

フィールドには複数値をとるかどうかの設定があるが、アプリの仕様で複数値をとるなら複数値設定をアリにしておけばいい。 設定がどうであっても、XPagesフィールドに値を書き込むときの処理によって複数値になるかどうかが決まるのだが、こちらもやはりNotesクライアントで文書を編集したときに、 この設定に応じてデータが格納されるので、大事な設定。

JSON形式のデータを格納するフィールドは複数値設定をはずしておくこと。Notesクライアントで保存しなおすと、JSONデータが崩壊する。

デフォルトアクセス用のフィールド

読者フィールドを用意して、アクセス制御するときに、設計ミスによって、変なユーザー名を入れてしまうと、Notesクライアントから開いても読めない文書ができてしまうことがある。 そのようなケースに備えて、動的にアクセス権を制御するフィールドとは別に、[デフォルトアクセス]みたいな名前の作成者フィールドを用意しておき、文書保存時にはそこに "[メンテ]"のようなロール名をセットするようにしておくといい。

Notesクライアントのユーザーをそのロールに組み込んでおけば、Notesクライアントからならいつでも開けるようになる。

まぁ、最終手段はDomino Administratorの「フルアクセスアドミニストレーション」モードを使ってアクセス権を再設定してやればいいわけだが。。

ビューの用意

ビューはXPagesで文書の一覧表示、目的の文書を検索、キーワードの抽出などいろんな用途に使う。これらはXPagesの設計をはじめたあとに必要に応じて作ればいいだろう。

あと、サンプルデータをNotesクライアントで入力したり、編集したりするためのビューは用意しておく。

XPagesの場合、一般ユーザーがNotesクライアントでアクセスしてくることはないので、"(検索用)"のようなカッコ付の隠しビューにする必要はない。

XPage

ブラウザで画面を開くのがXPageになるので、各画面の入口になる。

"XPages"は技術というか、プラットフォームの名前。XPagesアプリの各画面は単数形の"XPage"と記載する。

XPage

まずは名前。URLのこの部分になる。
フォームの設計

短くてURLに使って、画面の内容がある程度推測できるいい名前を付けよう。

XPageの設計

画面のレイアウトや機能を1つのXPagesに組み込むことは避ける。コードの見通しをよくするために、いくつかのパーツにわけて設計していく。

画面のパーツは「カスタムコントロール」で定義するので、各画面のトップであるXPageでは、こういうことをやる。

  • データソースの定義
  • JavaScriptライブラリやCSSなどのリソースの組み込み
  • 基本レイアウトを決めるカスタムコントロールの読み込み

アプリの種類にもよるだろうけど、こんなレイアウトが基本になると思う。
フォームの設計

XPageは基本、こんな記述になる。

  01: <?xml version="1.0" encoding="UTF-8"?>
  02: <xp:view xmlns:xp="http://www.ibm.com/xsp/core"
  03:     xmlns:xc="http://www.ibm.com/xsp/custom">
  04:     <xp:this.resources>
  05:         <xp:script src="/common.jss" clientSide="false"></xp:script>
  06:     </xp:this.resources>
  07:     <xc:xcLayout>
  08:         <xp:this.facets>
  09:             <xc:xcForm xp:key="facetBody" />
  10:         </xp:this.facets>
  11:     </xc:xcLayout>
  12: </xp:view>
  
  • 7行目の<xc:xcLayout>がヘッダやメニューなどのレイアウトを決めている(後で説明)
  • 9行目の<xc:Form>が画面のメインのコンテンツになる。xp:keyで指定してるのは、組み込み位置を決めるためのお約束。
  • この方法なら、9行目を変えるだけで、メインのコンテンツをいろいろ変えられるし、<xc:ccLayout>の中身を変えると、全ページのレイアウトを統一的に変更できるというメリットがある

レイアウトを決める<xc:xcLayout>

全頁のレイアウトを統一的に決めているのは<xc:xcLayout>というカスタムコントロール。

  01: <?xml version="1.0" encoding="UTF-8"?>
  02: <xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xc="http://www.ibm.com/xsp/custom">
  03:     <div class="lotusFrame">
  04:         <xc:xcBanner></xc:xcBanner>
  05:         <xc:xcTitleBar></xc:xcTitleBar>
  06:         <div class="lotusMain">
  07:             <xc:xcMenu></xc:xcMenu>
  08:             <div class="lotusContent">
  09:                 <xp:callback facetName="facetBody" id="facetBody"></xp:callback>
  10:             </div>
  11:         </div>
  12:         <xc:xcFooter></xc:xcFooter>
  13:     </div>
  14: </xp:view>
  
  • 3行目や6行目で指定してるclass="lotusFrame"とか"lotusMenu"がOneUIということになるのだろうか。
  • <xc:Banner>や<xc:TitleBar>はバナーやタイトルバーのカスタムコントロール。
  • 9行目にトップのXPageで指定したメインコンテンツのカスタムコントロールが組み込まれる。
  • こういう仕組みで、<xc:xcLayout>はそのままで、いろんなメインコンテンツを切り替えることができる

カスタムコントロール

カスタムコントロールはメニューやヘッダだけでなく、メインコンテンツの内容を決めていく。

そこそこ複雑な構成の画面だと、各パーツごとにカスタムコントロールにしておくと、見通しがよくなる。 そのとき、同じメインコンテンツのカスタムコントロールがカスタムコントロールの一覧に集まって表示されるように、ファイル名をあわせておくといいと思う。

例えば、ある画面のXPageが"board.xsp"だとすると、そこで<xc:xcLayout>に組み込むメインのカスタムコントロールを"boardCC.xsp"、その中の各パーツをこんな感じで名前付けしていくといい。

  • boardTop.xsp : 画面の上の方にあるコンテンツの基本情報を表示するエリア
  • boardEditBox: 編集画面で情報を入力するエリア
  • boardDispBox: 表示モードで情報を表示するエリア
  • boardFileUpload: 添付ファイルをアップロードする領域
  • boardActionButtons: 操作ボタンのエリア

などなど。

これを各XPageごとに用意しておけば、カスタムコントロールの一覧を開いたときに、目的のカスタムコントロールを探しやすくなる。

あと、画面によらず、共通に使えるカスタムコントロールは"ccManagement"みたいに、"cc"+機能名みたいに統一しておくといい。

SSJS(ServerSide JavaScript)

ちょっと大き目のXPagesアプリケーションの開発となると、いろんな処理を行うSSJSを用意する必要がある。 たいてい出てくる処理としては、

  • 文書を開く時の処理
  • 保存時の処理
  • ページ内の各パーツの表示/非表示の判定

他にも、何かの情報の一覧を扱うクラスのようなものも出てくる。

SSJSのスクリプトライブラリは以下のように分類できる。

  • あるページに関する処理を集めたライブラリ
  • ページライブラリの共通処理を集めたライブラリ
  • アプリによらず汎用的な処理をあつめたライブラリ
  • 情報リストを扱うクラス用ライブラリ

ページに関する処理を集めたライブラリには、そのページのXPage名と同じファイル名を付けておくとわかりやすい。 例えば、board.xspというページに関するライブラリは、board.jssという名前にしておく。

ページライブラリから呼ばれる共通の処理は、今回のアプリ特有の処理と、デバッグ関係などアプリによらず汎用的な処理がある。 アプリ特有の処理は、あまり使い回しができないような処理を集めておくといい。ファイル名はappCommon.jssなど。

アプリによらず汎用的に使えるライブラリはcommon.jssのようなファイル名を付けて、便利な処理を集めておくと、いろんなアプリで使い回しができていい。 あと、ユーザーの漢字名、所属、メールアドレスなど、アカウントに関する処理も使い回しができるが、アカウント関係の処理は結構多岐にわたるので、 account.jssのように別ファイルに分けておいたほうがいいだろう。

あとは、情報リストを扱うライブラリだが、クラス名をそのままファイル名にするといい。例えば、部品のリストを扱うクラス名がPartsListだったらPartsList.jssにする。

余談だが、機能別にライブラリを用意するという方法もあると思うが、たぶんやめておいたほうがいい。例えば、文書を開く時の処理や保存関係の処理を集めると、ページごとに似て非なる関数が 集まってくるので、けっこうごちゃごちゃしてくる。また、将来、別のアプリに機能別ライブラリを移植しようとしても、いろんなページの処理がごった混ぜになっているので、手直しが多くなる。 実際やってみたが、ページごとにまとめる方が無難だと思う。

ページ特有のライブラリの書き方

ページ特有のライブラリは、XPageやカスタムコントロールから呼び出される公開用の関数(メソッド)と、ライブラリ内だけで使われる関数がある。前者を「パブリックメソッド」、 後者を「プライベートメソッド」と呼ぶ。

ライブラリ内に、フラットにメソッドを定義すると、そのメソッドがパブリックなものか、プライベートなものかは、一見するとよくわからないので、メソッド名の先頭を'_'で始めると、 プライベートメソッドであるというルールを付けることになる。

  // xxxライブラリ
  function method_A() {
  }
  
  function method_B() {
  }
  
  // ここから下はプライベートメソッド
  function _private_A() {
  }
  
  function _private_B() {
  }
  

ただ、できればパブリックメソッドとプライベートメソッドが機構的に分離できたほうがいい。

JavaScriptでパブリックメソッド、プライベートメソッドっぽい記述をする方法を探すと、以下のような方法があるらしい。 参考にしたのは、「JavaScriptの名前空間定義チートシート」。

こういう書き方ができる。

  $common = {};
  (function() {
    // Public Methods
    $common.method_A = function(params) {
      _priv_func_A(params);
    }
    $common.method_B = function(params) {
      _priv_func_B(params);
    }
  
    // Private Methods
    function _priv_func_A(params) {
      //...
    }
    function _priv_func_B(params) {
      //...
    }
  })();
  
  • method_A()とmethod_B()のパブリックメソッドを$commonというオブジェクトに組み込んでいる。
  • プライベートメソッドはちょっと定義の方法が変わってるのがわかる
  • 外から呼び出すときには$common.method_A()のように呼び出す
  • _priv_func_A()の方は、$common._priv_func_A()でようには呼べなくなってる。$commonの中では、普通に_priv_func_A()のように呼べる。まさにプライベートメソッド

この方法だと、既存のライブラリをこの方式に変えるのは簡単。 ページの処理をまとめたライブラリであれば、$commonの部分の名前はページ名とあわせておけばいい。

ページ関係のライブラリの構成

ページ関係のライブラリで必ず出てくるのが、こんな処理。

  • 文書を開く時の処理
  • 文書を保存するときの処理
  • 編集、削除、承認などのアクションボタンの処理
  • ページ内パーツの表示を制御する処理

保存時の処理として、サブ的な処理としては、こんな処理もある。

  • 文書のアクセス権の制御
  • 文書の所在やステータスを制御
  • 通知メールの処理
  • 変更履歴

これらをページ関係のライブラリに組み込んでいく。

ページ関係のライブラリのメソッド名の例

どんなページでも出てくる処理に、同じメソッド名を付けておくと、統一感が出てイケてる感じになるし、メソッド名を見ただけで何をやろうとしてるのか 把握できるようになる。もちろん、処理の移植も簡単になる。

私の場合、ページ関係のライブラリでは、こんな感じのメソッドを用意してる。 人物情報表示のページの名前がperson.xspだとすると、こんな感じになる。もちろん、各メソッドの中身はページごとに異なる。

  // 人物表示ページライブラリ
  $person = {};
  (function() {
    // Public Methods
  
    // 文書Open時の処理
    $person.postOpenDoc = function(xsp_doc) {
    }
    // 文書新規作成時の処理
    $person.postNewDoc = function(xsp_doc) {
    }
    // 文書保存時の処理
    $person.saveDoc = function(xsp_doc) {
    }
    // xxxの表示判定
    $person.isDispXXX = function(xsp_doc) {
    }
    // ---- Private Methods -----
    // アクセス権の設定
    function _setAccessPermission(xsp_doc) {
    }
    // 文書のステータスの更新
    function _updateDocStatus(xsp_doc) {
    }
    // 通知メールの送信
    function _sendMail(xsp_doc, mailAddList) {
    }
    // 変更履歴の保存
    function _recordModifyHistory(xsp_doc) {
    }
  })();
  

一応、この仕組みの説明を。

  • これ、JavaScriptのことをよく理解しないと意味が理解できないんだけど、"(function() {...})()"の部分は「無名関数」というやつで、以下のように書き換えられる。

      var proc = function() {...};
      proc()
      
  • 要は、関数群を定義する関数オブジェクトを定義して、そのまま実行してる。
  • つまり、以下の行を連続実行しているようなものだ。まぁこれで$person内に関数オブジェクトを定義している。

      $person.postOpenDoc = function(xsp_doc) {...}
      $person.postONewDoc = function(xsp_doc) {...}
      
  • プライベートメソッドの方は、こういう書き方をすると、無名関数生成時に動的に生成される領域に保持される。無名関数の領域は無名関数ごとに別々に作成されるので、その無名関数の領域 に属するモノしかアクセスできない。そのため、プライベートメソッド的なことができるようになっている。

XPageやカスタムコントロール内からのページ内ライブラリの呼び出し

XPageやカスタムコントロール内から、ページライブラリのメソッドを呼び出す方法を説明する。

以下のコードはXPageのコード。これはページのbeforePageLoadイベントの処理になる。まぁページを開く前に呼ばれる処理と思えばいい。 これで$form.postOpenDoc()が呼ばれる。

  <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.beforePageLoad>
        <![CDATA[#{javascript:$form.postOpenDoc(document1);}]]>
    </xp:this.beforePageLoad>
    <xp:this.data>
        <xp:dominoDocument var="document1" formName="main"></xp:dominoDocument>
    </xp:this.data>
  

スクリプトはこんな感じの処理を書く。

  $form.postOpenDoc = function(xsp_doc) {
      $common.debug("$form.postOpenDoc() is called.");
      if (xsp_doc.isNewNote()) {
          var now = @Now();
          var dateStamp = [@Year(now), @Month(now), @Day(now)].join('/');
          xsp_doc.setValue("作成日", dateStamp);
      } else {
          var json_text = xsp_doc.getItemValueString("リスト情報");
          viewScope.myList = fromjson(json_text);
      }
  }
  

アカウントライブラリの紹介

ユーザーアカウントのいろんなサービスを提供してくれるライブラリの紹介。

ちょっと量が多めになるので、「アカウントライブラリ」を参照。