2025/10/19

共通部品にチャレンジ:#13)LotusScript 実行ログ - クラスに書き換え

汎用ライブラリは、できる限り組み込みやすい状態するため、クラス化することをお薦めしました。今回は、これまでに作成したライブラリをクラス化する作業を行います。


クラスの新規作成

まずは、クラスの器を作成します。クラス名は StdLog とします。

スクリプトライブラリの Options セクションに Public Class StdLog と入力し、Enter キーを押します。新しいクラス StdLog が定義され、画面が切り替わります。


変数の移設

Declarations に定義した Private 変数から、クラスごとに値を保持させるべき変数宣言をクラス内に移動します。

NotesSession や 現在の DB(xndbCur)は不変的なので、Declarations に残したままとします。

ログ DB(xndbLog)は、将来ログ出力 DB を指定する機能を作成する可能性もあるので、クラス内に移動しています。


関数の移設

Initialize と Terminate を除くすべての関数をクラス内に移動します。

Public の関数はそのままでクラス外からアクセスできるメソッドとなり、Private な関数はクラス内だけで利用できる関数となります。

移設が完了するとスクリプトライブラリのオブジェクトは下図のようになります。


初期化処理の移設

Initialize の処理からクラス単位で行う処理をクラスの初期化処理に移設します。

具体的には、Declarations に残した変数 xns と xndbCur の処理(赤枠)以外をクラス内の New サブルーチンに移設します。

なお、クラス化に当たり、ログの名称はクラスの初期化で指定することとします。よって、New にログ名称を受け取る変数 vsLogTitle を引数として定義しています。

ログの名称は、クラス内の変数 xsLogTitle で保管します。下図の通り変数の定義と New の引数で受け取ったログ名称を保管するよう追加します(赤枠)。


LogOpen の修正

ログの名称を New で指定するように変更したので、LogOpen の引数を削除します(紫枠)。ログ文書の初期化処理では、前段で保管した xsLogTitle を使って処理するように変更します(赤枠)。


終了処理の調整

Terminate で行っていた終了処理をクラス内に移設します。初期化 Initialize と同様に Delete サブルーチンを追加して、その中に移動します。


クラス化完了

ここまでの作業が完了すると、下図のようにエラーのない状態になっているはずです。

最後は機能には関係しませんが、コードを見やすくするために命名規則にあわせる調整を行います。今回はクラス内に定義されたプライベートな変数と関数の接頭文字を修正します。変数の命名規則ついては こちら にまとめています(下表はその抜粋)。

接頭語 用途
x スクリプトライブラリやフォーム内で有効な変数(パブリックではない)。
z クラス変数などスコープがクラス内に限定される場合に指定。
x と区別するために z とする。

関数名の接頭文字を zx としました。修正作業が完了すると以下のようになります。インターフェースとなるプロパティやメソッドとクラス内の Private 変数、関数が分離されて表示されるので見やすくできます。

次回は、このクラスに合わせてサンプルエージェントを修正します。


前回 共通部品にチャレンジ


2025/10/18

共通部品にチャレンジ:#12)LotusScript 実行ログ - 実際の利用と課題

前回までで LotusScript 実行ログを出力するライブラリ StdLog を作成しました。既存プログラムでこの機能を利用して、ログを出力するには、まずライブラリを DB 内にコピーしたうえで、Use で組み込みます。

Use "StdLog"

するとライブラリ内の Public な関数を利用してログ出力できます。提供する関数は次の通りでした。

関数名 機能
LogOpen ログをオープンします
PrintLog ログ(通常メッセージ)を出力します
PrintError エラーログを出力します
PrintSummary サマリーログを出力します
LogClose ログをクローズし、ログ文書を保存します

ここで問題となるのが、関数名の重複です。既存プログラム内に同名の関数がすでに存在した場合、そのままでは利用できません。かなり極端な事例ですが、次のような感じです。


この先ライブラリを機能拡張すると Public な関数が増えることになります。同様のエラーが発生する可能性が高くなり、まずます組み込みにくいライブラリとなってしまいます。


クラス化で解決

この問題を改善するのがクラス化です。ノーツの文書を取り扱う機能が NotesDocument クラスにまとまっているように、実行ログを出力する機能をユーザ定義クラスとしてまとめてしまう方法です。

次の例では、StdLog というクラスを作成して、ログ出力を実現しています。クラス内の PrintLog メソッドとエージェントの PrintLog サブルーチンはスコープが別になるのでエラーとなりません。将来、機能拡張があっても既存プログラムの影響は少ないことがわかりますね。

なお、クラス化については別の連載『クラス化に挑戦』でまとめています。必要に応じてを参照ください。


柔軟な利用

クラス化によって、ログ出力機能をオブジェクト内に完結させることができます。

これにより、1 つのエージェント内で複数のログ出力オブジェクトを同時に扱うことが可能になります。たとえば、デバッグ用とユーザ向けのログを分けて出力することで、見る人に応じた内容を整理して記録できます。

   Dim oLogAdmin As New StdLog("デバッグ用の詳細ログ")
   Dim oLogUser As New StdLog("ユーザ向け処理結果")

さらに、oLogAdmin や oLogUser を引数として他の関数に渡すことで、プログラムの意図がより明確になり、独立性の高い構造を実現できます。


開発効率の向上

クラス化する利点はほかにもあります。

組み込みのノーツクラスと同様に . を入力すると、利用できるプロパティやメソッドがリストアップされ、タイプアヘッドで選択できます。さらにライブラリに記載したコメントがポップアップヘルプとして表示されます。

コメントをしっかり書いておけば、ライブラリを開くことなく利用できますので、開発効率が劇的に上がります。


まとめ

今回は、通常の関数で作成したライブラリとクラス化したライブラリの差についてまとめました。クラス化したほうが汎用性が高くなり、ライブラリをより活用できることが確認できたかと思います。

そこで次回は、前回まで作成したライブラリをクラス化する作業を行います。


前回 共通部品にチャレンジ 次回


2025/10/16

文書の貼り付け禁止

ノーツはクリップボードを使用して文書(データ)を簡単に複製(レプリカではない)できます。これはこれで便利なのですが、アプリの仕様によっては制限したい場合があります。

そこで今回は、文書の貼り付けを禁止する方法についてまとめます。


実現方法

文書の貼り付け操作を行うとビューで 2 つのイベントが発生します。QueryPaste は貼り付ける前、PostPaste は貼り付けた後に発生します。今回は貼り付け操作を制御するので QueryPaste イベントを利用することになります。

他の Query??? イベントと同じく引数に Continue が存在します。この変数に False を設定すると貼り付けがキャンセルできるということですね。

Sub QueryPaste(Source As NotesUIView, Continue As Variant)
   Continue = False
End Sub


実用的なサンプル

上記のように、なんのメッセージもなしにキャンセルするとユーザは何が起こったのかわかりません。このアプリでは、文書の貼り付けを制限していると伝えてあげるべきです。

また、アプリの運用担当者など特権ユーザだけは、貼り付けを許可したい場合もあるかと思います。このような機能を実現するサンプルコードを紹介します。

Sub Querypaste(Source As Notesuiview, Continue As Variant)
   Dim v As Variant
   Dim i As Integer
   Dim sRole As String
   Dim sMsg As String

   ' 初期化
   Continue = False      ' いったん貼り付け禁止
   sRole = "[Admin]"    ' 貼り付け OK のロール
   sMsg = "アプリの仕様上、文書の貼り付けは制限されています。"

   ' OK のロールを持っているなら "OK" を返す
   v = Evaluate(|@If(@IsMember("| & sRole & |"; @UserRoles); "OK"; "NG")|)

   If v(0) = "OK" Then
      ' OK なので実行を確認
      i = Msgbox(sMsg & Chr(10) & "それでも貼り付けますか?", 36+256, sRole & " 保持者")
      If i = 6 Then
         ' [はい] をクリック = 貼り付け OK
         Continue = True
      End If
   Else
      ' 一般の利用者
      Msgbox sMsg, 16, "文書貼り付け"
   End If
End Sub

特権ユーザはロールを保持しているかで判定しています。初期化で sRole にそのロール名をセットしていますので、違う名称で判定したい場合にはこの値を調整してください。

実行すると保持しているロールに応じてメッセージが変わります。



まとめ

今回は文書の貼り付けの制御についてまとめました。

この機能はビューのイベントに設定します。ビューのイベントはビューごとに存在しますので、ユーザが表示できるすべてのビューに同じようにセットしないと抜け道ができてしまいます。特に後からビューを追加する場合に忘れがちなので、注意してください。


2025/10/14

Environ 関数 (LotusScript)

先日、Dominoデザイナーヘルプを見ていて Environ 関数をたまたま発見しました。LotusScript を使い始めてほぼ 30 年になるのですが、恥ずかしながらこの関数の存在を知りませんでした。

今回は備忘録を兼ねて Environ 関数についてまとめます。


Environ 関数

Windows の環境変数を取得する関数です。構文は次の通り。

Environ[$] ( { environName | n } )

引数は以下のどちらかを指定します。

1 environName  文字列 環境変数名
n 数値 n 番目の環境変数

戻り値は、Environ$ とした場合は文字列、Environ の場合は Variant 型となります。


環境変数の取得

以前『Windows のテンポラリフォルダの取得』の記事では、Windows API を使う方法を紹介しました。しかし、Environ 関数を使えば、1 行で解決できます。

sTmpFol = Environ$("TEMP")

こんな便利な命令があったんですね。デザイナーヘルプをもっと隅々までチェックしておけばよかったです...。


テンポラリフォルダ以外にもさまざまな環境変数があります。使いそうなものを抜粋すると次の通りです。

TEMP
TMP
テンポラリフォルダ
HOMEDRIVE ホームドライブ
HOMEPATH ホームパス
Path パスの一覧
OneDrive OneDrive フォルダ
windir Windows フォルダ

また、環境変数というだけあって、Windows や PC に関する基本的な情報も取得できます。

USERNAME ユーザ名
COMPUTERNAME コンピュータ名
PROCESSOR_ARCHITEW6432 CPU
PROCESSOR_IDENTIFIER CPU の詳細


環境変数の種類

引数に数値を指定すると n 番目の環境変数が取得できます。これを利用してすべての環境変数を順に確認することができます。

Option Declare

Sub Initialize
   Dim b As Boolean
   Dim i As Integer

   i = 0
   Do
      i = i + 1
      b = ShowEnv_n(i)
   Loop Until b = False
End Sub

Function ShowEnv_n(ByVal viIndex As Integer) As Boolean
   Dim s As String

   s = Environ$(viIndex)
   If s <> "" Then
      MsgBox CStr(viIndex) & ") " & s
      ShowEnv_n = True
   End If
End Function

実行すると、次のようになります。


上記のサンプルのように、引数に数値を指定した場合は「環境変数名=環境変数の値」を返します。引数に環境変数名を指定すると値だけを返すので注意してください。


2025/10/13

クラス化に挑戦: #14)クラス化とコメント

クラス化は開発効率を高めるために有効な開発手法となるのですが、コメントの書き方でさらに効果を高めることが可能です。


メソッドのコメント

一般的に通常の関数には関数の機能や使い方をヘッダコメントに記述します。クラスのメソッドも同様です。以下のコードは、前回 Location クラスに追加した CalcDistance メソッドにコメントを追加した例です。メソッドの使い方、引数の型と説明、戻り値、必要に応じて利用時の注意などを記載します。

%REM
自身の GPS 座標と引数で指定した座標との距離(m)を返します。

◆ 引数
voDestination Location 距離を求める GPS 座標

◆ データ型(戻り値)  Double
%END REM

   Public Function CalcDistance(voDestination As Location) As Double
      CalcDistance = distance(zdLatitude, zdLongitude, voDestination.Latitude, voDestination.Longitude)
   End Function

このようにメソッドの直上に記述すると、デザイナーでメソッドを使用する際にポップアップヘルプとして表示されます。

クラスの利用者が欲するコメントを書いておくと、それだけで開発がすすめられます。ただのコード内のコメントではなくなりますので、記述するモチベーションが上がりますね。


プロパティのコメント

プロパティの定義は Get / Set の 2 つのモジュールに分かれます。コーディング上はそれぞれコメントを記述できますが、ポップアップヘルプに表示されるのは先に記述されたコメントとなります。ですので Get と Set のモジュールは連続して記述してその上にコメントを書くといいでしょう。

%REM
検索キーワードを表します。

◆ データ型  String
◆ アクセス  取得、設定
%END REM

   Public Property Get Keyword As String
      Keyword = zsKeyword
   End Property

   Public Property Set Keyword As String
      zsKeyword = Keyword
   End Property


クラスのコメント

クラス定義の上に記述したコメントがポップアップヘルプとして利用されます。このヘルプはクラス定義に対してと New でそのクラスを選んだ場合とで共通です。


ですので、記述するコメントはクラスの機能とオブジェクトの作成方法である New の引数を記述しておきましょう。また、このクラスで必要となる他の他の設計要素についても記述すると間違いなく利用できますね。

%REM
Location クラス ( lsGoogleMap ライブラリ )
---------------------------------------------------
GPS の位置情報を管理します
 
◆ オブジェクトの作成 ( New の引数 )
vdLatitude      Double   緯度
vdLongitude  Double   経度

◆ 関連設計要素
  (なし)
%END REM

Public Class Location
         ・・・
   Public Sub New(ByVal vdLatitude As Double, ByVal vdLongitude As Double)
         ・・・
   End Sub
         ・・・
End Class


まとめ

今回はクラスライブラリのコメントと効果的な使い方についてまとめました。汎用的なクラスになればなるほど、自分以外の開発者が使うことになるので、それを意識してわかりやすく、使いやすいコメントを記述するようにしましょう。


前回 クラス化に挑戦


2025/10/12

ビューのカテゴリと読者権限

前回はビューに合計を表示する方法と読者権限利用時の注意について記載しましたが、ビューのカテゴリに関しても類似した注意が必要です。例えば、以下の画面のようにカテゴリは表示されているのですが、カテゴリを開いても文書が表示されないという現象があります。

この現象は、カテゴリ内の文書すべてに対して参照権限がない場合に発生します。参照権限がある([Admin] ロールを持つ)ユーザでビューを確認すると、カテゴリ内の文書が表示されます。このカテゴリ部分だけが見えていたということになります。


対応策

この問題はビューのプロパティで回避できます。[スタイル]タブで『空のカテゴリを表示しない』にチェックを入れると解消できます。

設定後のビューではカテゴリは消えていることが確認できます。こんな機能があるのであれば、合計が一致しない問題も改善してほしくなりますね。


裏技

『空のカテゴリを表示しない』のチェックを外すと文書は見えなくてもカテゴリが表示されました。この挙動を利用した裏技があります。カテゴリしか表示されていなくても、@DbColumn で取得することができるんです。


@DbColumn("":"NoCache"; "":""; "vAllCategories"; 1)

参照権限を設定している文書が集まるアプリで、カテゴリの選択肢だけは共通化したい場合に利用できます。参照権限がない文書から(部分的ですが)値を取得する裏技ということですね。


2025/10/11

ビューの合計と読者権限

ノーツのビューには合計を表示する機能があります。次の画面ではカテゴリ内の合計をカテゴリと同列に表示、総合計を最終行に表示しています。合計はカテゴリを閉じても表示されているので、データの概要をつかむのに便利です。Excel の小計機能のようなものですね。


合計の出し方

まず、合計を出したい列(今回は単価)のプロパティの[ソート]タブの一番下の項目、合計で ”合計” を選択します。これだけで合計が出力されます。

次に、ビューのプロパティの[スタイル]タブで合計の色を指定します。そのままだと各文書の単価と合計が同じ色で表示されて見にくくなるので、必ず設定しましょう。

たったこれだけの操作で合計を出力できます。


読者権限設定時は注意

合計は簡単で便利な機能なのですが、弱点もあります。

最初の画像の最終列が読者フィールドの値で、3 文書で参照権限を制限しています。[Admin] ロールを持たないユーザに切り替えてこのビューを表示します。すると文書は見えないのに、合計には合算されています。

合計金額は、最初の画像と同じとなっており、全件分の合計が必ず表示されるようです。読者フィールドを利用したアプリで合計を使う場合、注意しましょう。


2025/10/10

@Subset の使い方

今回は @Subset という関数の使い方をまとめます。リスト値(複数値、配列)を操作する上で必須となる関数です。


@Subset の仕様と構文

@Subset はリスト値から部分リスト値を抽出する場合に使用します。構文は次の通りです。

@Subset( list ; number )

1 list 文字列リスト
数値リスト
日付/時刻値リスト
抽出元のリスト値
2 number 数値 抽出する値の数


@Subset の動作

まずは単純な例です。list に次の 5 つの値が入っていて、3 を指定した場合の結果です。リストの前から指定した数だけのリスト値が返されます。

list @Subset( list ; 3 )
@関数
LotusScript
HTML
JavaScript
Java
@関数
LotusScript
HTML

おもしろいのは負の数を指定できることです。この場合、リストの後ろから指定した数だけ取得します。例えば -3 を指定した場合は次のようになります。

list @Subset( list ; -3 )
@関数
LotusScript
HTML
JavaScript
Java
HTML
JavaScript
Java

リスト値の要素数を意識することなく、一発で後ろから取得できるなんて高性能な関数ですね。LotusScript で記述すると 1 行では済まないので、@関数が便利だと感じる瞬間ですね。


指定した要素番号の値を取得

1 を指定するとリスト値の先頭、-1 を指定すると最後の要素を取得できます。この機能を利用して、要素番号 n の値だけを取得するには次のように記述します。

@Subset(@Subset(list; n); -1);

上記はリストの前から n 番目を取得しますが、次のように記述すると後ろから取得できます。

@Subset(@Subset(list; -n); 1);

リストの要素のアクセスは list[3] のように [ ] で指定することができます。ただ、この機能は Notes Release 6 からの新機能(20 年以上前です)で、それ以前から Notes 界隈にいる人は今でも @Subset 二段掛けを使う人が多いと思います。


まとめ

今回は @Subset の使い方についてまとめました。このブログ内ですでに何度も登場している関数ですね。他の事例については当ブログ内の 出直し!!ヘルプ @関数編 @Subset 内に「関連記事」としてまとめています。


2025/10/03

共通部品にチャレンジ:#11)LotusScript 実行ログ - 実行環境の取得

今回 LotusScript 実行ログ出力機能に追加するのは、実行環境の情報を取得する機能です。バージョンや環境に依存する問題の確認に利用できます。


フォームの修正

ACL タブの後ろに ”実行環境” タブを追加します。そのタブ内にカスケード表を作成して、フィールドを配置します。


項目 フィールド名 種類 補足
バージョン(ビルド) NotesVersion テキスト 編集可能
NotesBuildVersion
@Platform Platform
@Platform([Specific]) PlatformSpecific


ライブラリの修正

新規追加した上記フィールドをセットするコードをライブラリに追加します。実行環境も ACL 情報と同じく、実行につき一度だけの取得となるので、初期化処理に追加します。

Private Function xInitLog(ByVal vsLogTitle As String)
         ・・・
   'アクセスレベル
   Dim lOpt As Long
   xndLog.AccessLevel = CStr(xndbCur.QueryAccess(sName))
   lOpt = xndbCur.QueryAccessPrivileges(sName)
   xndLog.AccessOption = xGetACLOptionName(lOpt)
   xndLog.UserRole = xndbCur.QueryAccessRoles(sName)

   '実行環境
   xndLog.NotesBuildVersion = xns.NotesBuildVersion
   xndLog.NotesVersion = xns.NotesVersion
   xndLog.Platform = Evaluate(|@Platform|)
   xndLog.PlatformSpecific = Evaluate(|@Platform([Specific])|)


   'リッチテキスト準備
   Set xnrti = xndLog.CreateRichTextItem("Body")
         ・・・

バージョン(ビルド)情報は NotesSession クラスから取得し、OS の情報は @Platform 関数を利用して取得しています。こんな時に Evaluate 関数は便利ですね。


まとめ

最近の LotusScript の実行環境は、Windows 上のノーツクライアントだけでなく、Nomad など多岐にわたります。ログにこの機能を付けておくとどのクライアントで発生した問題であるかを確認できますね。

これで、当初予定していた機能が完成しました。さまざまなアプリケーションに組み込んでログ効率的に管理し、サポートにかかる時間を極力削減しましょう。


前回 共通部品にチャレンジ 次回


2025/10/02

共通部品にチャレンジ:#10)LotusScript 実行ログ - ACL の取得

今回は LotusScript を実行しているユーザの ACL 情報を取得します。想定外のエラーの場合、権限に起因する場合もあります。この情報があればトラブル対応の時間短縮になるかもしれませんね。


フォームの修正

ログに ACL 情報を表示するためのフィールドを作成します。

新しいタブを追加(タブ表に 1 行追加)してタイトルに ”ACL” を指定します。そのタブ内にカスケード表を作成して、フィールドを配置します。

作成するフィールドは次の通りです。

項目 フィールド名 種類 補足
アクセスレベル AccessLevel ダイアログリスト 編集可能 キーワードは後述
ロール UserRole テキスト 複数値も可、改行区切り
オプション AccessOption チェックボックス キーワードは後

項目 種類 補足
アクセスレベル 選択肢を入力 なし|0
投稿者|1
読者|2
作成者|3
編集者|4
設計者|5
管理者|6
オプション 文書の作成|CreateDocuments
文書の削除|DeleteDocuments
個人エージェントの作成|CreatePersonalAgents
個人フォルダ/ビューの作成|CreatePersonalFoldersAndViews
共有フォルダ/ビューの作成|CreateSharedFoldersAndViews
LotusScript/Javaエージェントの作成|CreateLotusScriptJavaAgents
パブリック文書[読者]|ReadPublicDocuments
パブリック文書[作成者]|WritePublicDocuments
文書を複製またはコピー|ReplicateOrCopyDocuments

アクセスレベルとロールは重要な情報なので、基本情報のタブにも表示します。


項目 フィールド名 種類 式 / 補足
アクセスレベル AccessLevel_D ダイアログリスト 表示用の計算結果 AccessLevel
キーワードは 
AccessLevel と同じ
ロール UserRole_D テキスト UserRole
複数値も可、カンマ区切り


ライブラリの修正

続いて、追加したフィールドに値をセットするプログラムの作成です。ACL 情報は実行につき一度だけの取得でいいので、初期化処理に追加します。

Private Function xInitLog(ByVal vsLogTitle As String)
         ・・・
   'DB情報
   xndLog.DBTitle = xndbCur.Title
   xndLog.Server = xndbCur.Server
   xndLog.FilePath = xndbCur.FilePath

   'ACL 情報
   Dim lOpt As Long
   xndLog.AccessLevel = CStr(xndbCur.QueryAccess(sName))
   'アクセスレベル
   lOpt = xndbCur.QueryAccessPrivileges(sName)
   xndLog.AccessOption = xGetACLOptionName(lOpt)
                   'ACL オプション
   xndLog.UserRole = xndbCur.QueryAccessRoles(sName)
          'ロール

   'リッチテキスト準備
   Set xnrti = xndLog.CreateRichTextItem("Body")
         ・・・
End Function

QueryAccessPrivileges メソッドで取得した値は、フォームで利用するのは少し複雑です。そこで新しい関数 xGetACLOptionName で値を選択肢の文字列に変換しています。

End Function Private Function xGetACLOptionName(ByVal vlOpt As Long) As Variant
   Dim asOpt() As String
   Dim asReturn() As String
   Dim i As Integer
   Dim j As Integer

   '一度だけ実行なので定数化しない
   ReDim asOpt(8)
   asOpt(0) = |CreateDocuments|
   asOpt(1) = |DeleteDocuments|
   asOpt(2) = |CreatePersonalAgents|
   asOpt(3) = |CreatePersonalFoldersAndViews|
   asOpt(4) = |CreateSharedFoldersAndViews|
   asOpt(5) = |CreateLotusScriptJavaAgents|
   asOpt(6) = |ReadPublicDocuments|
   asOpt(7) = |WritePublicDocuments|
   asOpt(8) = |ReplicateOrCopyDocuments|

   'ビットごとに確認
   j = 0
   ReDim asReturn(j)
   For i = 0 To UBound(asOpt)
      If vlOpt And (2 ^ i) Then
         'ビットが立っているので戻り値の配列に追加
         ReDim Preserve asReturn(j)
         asReturn(j) = asOpt(i)
         j = j + 1
      End If
   Next

   xGetACLOptionName = asReturn
End Function

なお、ACL 情報の取得については、『アクセス権限とロールの取得 - LotusScript』と『QueryAccessPrivileges の判定と演算子の活用』で紹介しているので参照ください。


前回 共通部品にチャレンジ 次回