2009年12月28日月曜日

Excelファイルを読み込んで文書を作成する


マスターファイルとして使っているようなExcelファイルをNotesアプリケーションへ取り込みたいといったことはよくあります。
そんなときに雛型として利用している Lotus Script をご紹介します。

Sub Initialize
Dim ws As New NotesUIWorkspace
Dim db As NotesDatabase
Dim filepath As Variant
Dim xlapp As Variant
Dim xlbook As Variant
Dim xlsheet As Variant
Dim maxrows As Long
 
Set db = ws.CurrentDatabase.Database
 
On Error Goto ERRORHANDLER
 
filepath = ws.OpenFileDialog(False, "取り込むファイルを選択してください。 ", _
"EXCEL 97-2003|*.xls|EXCEL 2007|*.xlsx|CSV|*.csv|TXT|*.txt|すべて|*|")
 
If Isempty(filepath) Then Exit Sub
 
'選択したExcelファイルを開く、ただし画面には表示しない
Set xlapp = CreateObject("Excel.Application")
Set xlbook = xlapp.Workbooks.Open(filepath)
Set xlsheet = xlbook.Worksheets(1)
xlApp.Visible = False
 
'最終行の番号を調べる
maxrows = xlsheet.UsedRange.Rows(xlsheet.UsedRange.Rows.Count).Row
 
For rows = 1 To maxrows 'タイトル行がある場合、開始行を変更する
Set doc = New NotesDocument(db)
'ここから -都合にあわせて書き換える
Call doc.ReplaceItemValue("Form", "MainTopic")
Set item = doc.ReplaceItemValue("Category1", Cstr(xlsheet.Cells(rows, 1).Value))
Set item = doc.ReplaceItemValue("Category2", Cstr(xlsheet.Cells(rows, 2).Value))
'ここまで
Call doc.Save(True, True)
Set doc = Nothing
Next
 
ENDPROC: 'Excelを終了する
If Not xlapp Is Nothing Then
xlapp.quit
Set xlapp = Nothing
End If
Exit Sub

ERRORHANDLER:
Msgbox Str(Err) & ": " & Error$
Resume ENDPROC
End Sub

2009年12月25日金曜日

文書の「閉じかた」には気をつけろ!

ドミノ懇談室のこのスレッドに関連して
返答文書を保存したあと、親文書の値を書き換えて、画面を閉じる、といったアクションボタンの式をテストしていた時のこと、

(アクションボタンへは次のように3行だけ記述されています)
@Command([FileSave]);
@SetDocField($Ref; "LastUpdated"; @Now);
@Command([FileCloseWindow])

上記の「画面を閉じる」部分を @Command([CloseWindow]) に書き換えて実行したところ、NSDを出力してNotesが強制終了されました。
(Notes 8.0.1 Basicで確認)

試しに @PostedCommand([CloseWindow]) に書き換えたところ、これは正常に終了します。

結果です。
× @Command([CloseWindow])
○ @PostedCommand([CloseWindow])

○ @Command([FileCloseWindow])


正常に動作するのは、どれも他の式がすべて評価された後に実行されるものです。
上記3パターンのうち @Command([CloseWindow]) だけがすぐに実行されます。

いずれにしても、式の一番最後に記述されているのに挙動が異なる場合もあるのですね。

2009年12月23日水曜日

全文索引フォルダのサイズを調べる


全文索引を作成すると、アプリケーションのnsfファイルがあるフォルダに.ft で終わるフォルダが作成されます。

アプリケーションのファイル名が names.nsf の場合、フォルダ名は names.ft となります。わかりやすいですね。

これまでサイズを調べるには、アプリケーションのプロパティや Windows の Explorer でフォルダのプロパティを参照していました。
ただサーバー内のすべての全文索引について調べるには、これでは効率が悪すぎます。

そこでツールを作ろうと思い立ったわけです。

「添付ファイルを作成する」を有効にしている場合など.ftフォルダのサイズが大きいものではギガバイト単位に膨れ上がったりします。

Domino6.5.3の頃、そんなアプリケーションで誰かが全文検索した時にサーバーがクラッシュするといった不具合が時々あり、試しに「添付ファイルを作成する」を無効にして.ftフォルダのサイズを抑制したところ、全文検索時のクラッシュが再現しなくなった、といったこともありました。

下のプログラムは.ftフォルダのサイズを調べるツールとして作成したものです。Windows 2000 Serverで稼働しました。

Dim FSO, FLD, TF
Set FSO = WScript.CreateObject("Scripting.FileSystemObject")
Set FLD = FSO.GetFolder(".")
Set TF = FSO.CreateTextFile(FLD.Path & "\ftlist.csv", True)
ScanFolder FSO.GetFolder(FLD.Path), FSO, TF
TF.Close()
Set TF = Nothing
Set FLD = Nothing
set FSO = Nothing
WScript.Echo("全文索引フォルダのリストを出力しました。")

Sub ScanFolder(objFolder, objFSO, objFile)
 Const DQ = """"
 For Each o in objFolder.subFolders
  if Ucase(Right(o.Name, 3)) = ".FT" Then
   objFile.WriteLine DQ & o.Path & DQ & "," & DQ & o.Size & DQ
  Else
   ScanFolder objFSO.GetFolder(o), objFSO, objFile
  End If
 Next
End Sub

上記プログラムを ftlist.vbs などとして Notes\Data フォルダに保存します。

コマンドプロンプトから実行すると、notes\dataフォルダ配下の .ft フォルダの名前とそのサイズを ftlist.csv へ出力します。

ただ、ディレクトリリンクがあっても、その先まで追いかけませんが、そこは即席手抜きツールなのでご勘弁。

2009年12月21日月曜日

文書を編集できないよう制御する

アクセス制御リスト(ACL)上では便宜上、編集者としなければならないけれど、特定の条件下では閲覧できるけれども編集モードにさせたくない、といった場合がときどきあります。

編集モードで表示するには、表示モードで開いている状態から移行する他に、ビューから Ctrl + E 等で直接編集モードで開くといったパターンが考えられます。

表示モードから編集モードへ切り替える時、フォームのイベント Querymodechange で判定できます。
Sub Querymodechange( Source As Notesuidocument, Continue As Variant )
    If Not Source.EditMode Then
        If Isnull( Arraygetindex( Evaluate( "@UserRoles" ), "[Admin]", 5 ) ) Then Continue = False
    End If
End Sub

上のコードでは、Evaluate の戻り値が配列になるので Arraygetindex で配列のどこに管理者のロールがあるかを調べています。配列のどこにもなければ Null となり判定できます。


ビューから直接、編集モードで開く時、フォームのイベント QueryOpen で判定できます。
Sub Queryopen( Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant )
    If Not Isnewdoc Then
        If Mode = 1 Then
            dbadmin = Evaluate(|@IsMember( "[Admin]"; @UserRoles )|)
            If dbadmin( 0 ) = 0 Then Continue = False
        End If
    End If
End Sub

上のコードでは、@IsMember で管理者のロールが付与されているかどうかを調べています。ロールが付与されていなければ戻り値(配列)の0番目には0が入ります。

なお、新規文書の作成時には判定しないよう制御しています。
判定方法はいろいろあると思いますが、上のどちらも@関数とロールを活用してコードを簡単にしています。

2009年12月20日日曜日

しょっちゅう変更されるトップページの実現について


アプリケーションを開いたときに特定の文書をトップページ(ホーム/初期画面)として開きたい、といった要望がときどきあります。
 
変更頻度がほとんどないなら「アプリケーションについて」などで代用することを提案するのですが、中には「毎日更新する」という精力的な方々もいらっしゃいます。
 
けれど、アクセス制御リストにおいてアプリケーションの利用者を編集者以下として設定している場合、ページのような設計要素を変更することができません。
 
そういった時はトップページ専用のフォームを用意して、アプリケーションの利用者が文書を作成できるようにしています。
 
フォームにはトップページを変更するためのアクションボタンがあり、それをクリックすると文書のUNIDをプロフィール文書へ設定します。
このプロフィール文書は特定の個人専用ではなく、皆が共通で利用することを想定しています。
 
アプリケーションを開いたときに必ず開くフレームセットでは、その中のトップページを表示するフレームでURLの式を次のように設定しています。
 
serv := @Left(@Name([Abbreviate];@Subset(@DbName;1));"/");
dbn := @ReplaceSubstring(@Subset(@DbName; -1); "\\"; "/");
docid := @Text(@GetProfileField("CommonProfile"; "MessageDocumentUNID"; "Common"));
tmp := @GetDocField(docid; "Subject");
"notes://" + serv + "/" + dbn + @If(@IsNull(tmp) | @IsError(tmp); "/BLANKPAGE?OpenPage"; "/0/" + docid + "?OpenDocument")

万一、トップページ文書が削除された場合でもフレームセットがキチンと表示されるよう "BLANKPAGE" という名前のページを作成しています。 

2009年12月19日土曜日

カレンダーの現在の日付を新規文書へ反映させる


カレンダー形式のビューで新規文書を作成するアクション(日付の枠内をダブルクリック、あるいはビューのアクションボタンをクリック)したとき、アクティブになっている日付を、新規文書の日付欄へ自動で設定したい場合があります。
 
ダブルクリックした日付は NotesUIView.CalendarDateTime で取得することができます。
これをカレンダービューのイベント Regiondoubleclick で使います。
 
Sub Regiondoubleclick(Source As Notesuiview)
 Dim ws As New notesuiworkspace
 Dim uidoc As NotesUIDocument
 Dim doc As NotesDocument
 Set uidoc = ws.ComposeDocument("", "", "Schedule")
 Set doc = uidoc.Document
 doc.StartDate = Datevalue(Source.CalendarDateTime)
End Sub

 
アクティブな日付をカレンダービューのアクションボタンで取得するには、NotesUIWorkspace.CurrentCalendarDateTime を使います。
 
Sub Click(Source As Button)
 Dim ws As New notesuiworkspace
 Dim uidoc As NotesUIDocument
 Dim doc As NotesDocument
 Dim varDate As Variant
 varDate = ws.CurrentCalendarDateTime
 Set uidoc = ws.ComposeDocument("", "", "Schedule")
 Set doc = uidoc.Document
 doc.StartDate = varDate
End Sub
 
ちなみに、最後の行を
doc.StartDate = ws.CurrentCalendarDateTime
とすると値が入らないし、
doc.StartDate = Datevalue(ws.CurrentCalendarDateTime)
とするとすんなりいきません(Notes 8.0.1で確認)

2009年12月17日木曜日

文書リンクから非表示フィールドへアクセスする


フォームのプロパティで「印刷/転送/クリップボードへのコピーを不可」が有効な場合、そのフォームで作成した文書をプロパティで覗くと、各アイテムの値等がグレーアウトされます。
その状態ではスクロールもできないので、隠しフィールドにある値が確認できない、といったことがあります。
 
そこで、アイテムの値を簡単に確認できるものを作ってみました。
 
下記はフォーム上のホットスポットボタンに書いた Lotus Script です。
フォームにはこのボタンの他にリッチテキスト・フィールド "Body" があります。
 
ユーザーはこのリッチテキスト・フィールドへ対象文書のリンクを貼り付けた後、ボタンをクリックします。
すると文書内にある読者フィールドまたは作成者フィールドのアイテム名と値をステータスバーへ表示します。
 
Sub Click(Source As Button)
 Dim ws As New NotesUIWorkspace
 Dim db As New NotesDatabase("", "")
 Dim doc As NotesDocument, tdoc As NotesDocument
 Dim rti As NotesRichTextItem
 Dim rtnav As NotesRichTextNavigator
 Dim rtlink As NotesRichTextDocLink
 Dim DUMMYID As String, tmpType As String
 DUMMYID = String$(32, "0")

 Call ws.CurrentDocument.Refresh(True) '保存せずリンクへアクセスするための Refresh

 Set doc = ws.CurrentDocument.Document
 Set rti = doc.GetFirstItem("Body")
 Set rtnav = rti.CreateNavigator
 If rtnav.FindFirstElement(RTELEM_TYPE_DOCLINK) Then
  Set rtlink = rtnav.GetElement
  If rtlink.DocUnID <> DUMMYID Then
   If db.OpenByReplicaID(rtlink.ServerHint, rtlink.DbReplicaID) Then
    Set tdoc = db.GetDocumentByUNID(rtlink.DocUnID)
    If Not (tdoc Is Nothing) Then
     Forall item In tdoc.Items
      If item.IsAuthors Or item.IsReaders Then
       If item.IsAuthors Then
        tmpType = "AUTHORS"
       Else
        tmpType = "READERS"
       End If
       Print "Item : " & item.Name & " , Type : " & tmpType & " , Value : " & item.Text
      End If
     End Forall
    End If
   End If
  End If
 End If
End Sub

上記では文書にあるすべてのアイテムのタイプを調べていますが、アイテム名がわかっている場合はフォーム上で指定できてもいいかもしれませんね。
 
まあ、結果をPrint 文で表示しているあたり、いいかげんに作ったことがバレバレですが....失礼。
 
上記フォームのフィールド SaveOptions へ "0" をセットしておくと、すんなりとフォームを閉じることができます。

2009年12月16日水曜日

すべてのアプリケーションのACLエントリを書き出す

あるサーバー内に設置されたすべてのアプリケーションでアクセス権がどのように設定されているのか、ということを一気に書き出すエージェント(Lotus Script)を紹介します。

ACL取得専用のDBをひとつサーバーに設置して、エージェントを二つ登録します。

ひとつは、下に示すエージェントをサーバー上で実行する指示を出すエージェント、

もうひとつは、以下のとおりです。

処理のおおまかな流れとしては、

1. サーバー内のアプリケーションのリストを取得する
2. 各アプリケーションのACLエントリを書き出す

となっています。

サーバー内のアプリケーションのリストは、NotesSession.GetDbDirectory で取得できます。
リストからアプリケーションを1件取り出してはACLのエントリを書き出す、という処理を繰り返すわけです。

ひとつのACLエントリにつき1文書を書き出していきます。
Sub Initialize
    '処理を起動する
    Dim ss As New NotesSession
    Call dblist(ss)
End Sub

Sub dblist(ss as NotesSession)
    'サーバー上にあるアプリケーションのリストを取得、処理させる
    Dim dbdir As NotesDbDirectory
    Dim db As NotesDatabase
    Set dbdir = ss.GetDbDirectory(ss.CurrentDatabase.Server)
    Set db = dbdir.GetFirstDatabase(DATABASE)
    While Not (db Is Nothing)
        Call getaclentries(db, ss)
        Set db = dbdir.GetNextDatabase()
    Wend
End Sub

Sub getaclentries(db As NotesDatabase, ss as NotesSession)
    'アプリケーションのACLに登録されたエントリを書き出します。
    Dim notesacl As NotesACL
    Dim aclentry As NotesACLEntry
    Dim doc As NotesDocument

    If Not db.IsOpen Then flag = db.Open("", "")
    Set notesacl = db.ACL
    Set aclentry = notesacl.GetFirstEntry()
    While Not (aclentry Is Nothing)
        Set doc = New NotesDocument(ss.CurrentDatabase)
        With doc
            .Form = "ACLEntry"
            .Server = db.Server
            .DBFilePath = db.FilePath
            .DBTitle = db.Title
            .DBReplicaID = db.ReplicaID
            .CreateDate = CreateDate.LSLocalTime
            .CanCreateDocuments = OFFON(aclentry.CanCreateDocuments)
            .CanCreateLSOrJavaAgent = OFFON(aclentry.CanCreateLSOrJavaAgent)
            .CanCreatePersonalAgent = OFFON(aclentry.CanCreatePersonalAgent)
            .CanCreatePersonalFolder = OFFON(aclentry.CanCreatePersonalFolder)
            .CanCreateSharedFolder = OFFON(aclentry.CanCreateSharedFolder)
            .CanDeleteDocuments = OFFON(aclentry.CanDeleteDocuments)
            .CanReplicateOrCopyDocuments = OFFON(aclentry.CanReplicateOrCopyDocuments)
            .IsAdminReaderAuthor = OFFON(aclentry.IsAdminReaderAuthor)
            .IsAdminServer = OFFON(aclentry.IsAdminServer)
            .IsGroup = OFFON(aclentry.IsGroup)
            .IsPerson = OFFON(aclentry.IsPerson)
            .IsPublicReader = OFFON(aclentry.IsPublicReader)
            .IsPublicWriter = OFFON(aclentry.IsPublicWriter)
            .IsServer = OFFON(aclentry.IsServer)
            .Level = aclentry.Level
            .Name = aclentry.Name
            .Roles = aclentry.Roles
            .UserType = aclentry.UserType
            flag = .Save(True, True)
        End With
        Set aclentry = notesacl.GetNextEntry(aclentry)
        Set doc = Nothing
    Wend
End Sub

Function OFFON(flag As Variant) As String
    'True/Falseの値を扱いやすくするため、Trueを"1"、Falseを"0"に置き換えます
    If flag Then
        OFFON = "1"
    Else
        OFFON = "0"
    End If
End Function

ところで、サーバー管理者と言えど全てのアプリケーションへのアクセスが許可されていない場合があります。
そこで、NotesAgent.RunOnServer でエージェントをサーバー側で実行する際、サーバー側で実行するエージェントのプロパティにある「実行時セキュリティレベルの設定」は"3. フルアドミニストレータ権限で制限された操作を許可する"を選択します。


当方では、上記で書き出したリストを簡易な変更履歴として保管する仕組みを追加しています。といっても、前回までに取得したACLエントリ文書へフラグをたてて、最新のACLエントリ文書と区別できるようにしているだけですけどね。

2009年12月15日火曜日

日付の範囲をカレンダービューで表示する


日付の範囲が入力されたアイテムをプロパティ等でみてみると、ハイフンで区切られた二つの日付が見えます。

2009/12/15 - 2009/12/17

このアイテムをカレンダービューの1列目にそのまま設定しても、最初の日(上の例では12/15)だけにしか表示されません。

カレンダービューの12月15日、16日、17日の3日間すべてに文書を表示する場合、範囲をひとつひとつの日付に分解します。

そこで登場するのが @Explode 関数です。

@Explode 関数は特定の区切り文字で区切られた単一の値をリストに分解するものとして利用しますが、日付の範囲の場合、複数値に分割してくれます。

例えば次のような式の場合、"2009/12/15", "2009/12/16", "2009/12/17" の3つの値に分解されます。

@Explode([2009/12/15 - 2009/12/17])

ここで注意しなけれならないのは、戻り値が日付のリストではなくテキストのリストであることです。

そんな訳でカレンダービューの1列目に設定する場合、上記式の戻り値を @TextToTime 関数で日時形式へ変換します。

例えば StartEndDate フィールドに日付の範囲が入力されている場合、列式へは

@TextToTime(@Explode(StartEndDate))

と指定します。

そしてビューの1列目で、ソートを昇順、「複数値を別のエントリで表示」をオンにすればできあがりです。

2009年12月10日木曜日

Field: 'FIELDNAME': Too many arguments for database function

日本語版Notesではこのメッセージが


フィールド: FIELDNAME: データベースの関数に対して引数が多すぎます。

と表示されます。

これはR6.5で作られたDBの文書をR5のクライアントを使って開こうとしたときに表示されたメッセージです。

@DbLookup のパラメータとして使った [FAILSILENT] がR5では使えないのです。

Notesに限らず開発する場合には、利用者の中でも一番低いバージョンにあわせて開発するのがキホンです。

今回は知らない間にアクセス許可された海外事業所の利用者のことまで考えがおよばなかったという(言い訳するところも含めて)お粗末な見本でした。

2009年12月9日水曜日

アプリケーション管理者の扱い


ワークスペースからアイコンを削除するつもりでnsfファイルを削除してしまう...

単純な操作ミスですが、そんなトラブルを経験したことはないですか?

削除されたnsfファイルをバックアップテープからリストアするのは時間もかかるし面倒なんですよね。

過去に沢山そんな経験をしました。

アプリケーションへのアクセス権を変更するにはACLを変更できなければいけない、とするとACL上は「管理者」として登録しておかなければいけません。

でも管理者はnsfファイルもろとも削除することができてしまう。

そこでACLを触らずにアクセス権を変更できるようにするため、ドミノディレクトリのグループ文書を利用することにしました。

アプリケーション管理者へは権限の種類ごとに用意したグループ文書を編集できる権限を付与します。

そしてACLではアプリケーションの管理者へ付与する権限は「設計者」以下としました。

こうすることで、現在はリストア作業がほとんどなくなりました。

2009年12月8日火曜日

フレームセットの表示内容が表示されない


フレームセットを使ったアプリケーションで、ときどきフレームの中に何も表示されなくなる場合があります。

このような時、ブックマーク(bookmark.nsf)に格納された情報を削除すると元通りになることがあります。

削除方法
  1. アプリケーションのプロパティの情報タブにあるレプリカIDをメモします。
  2. ワークスペースからブックマークのアイコンを右クリックして、Ctrl キーと Shift キーを押しながら「アプリケーション - 移動」をクリックします。
  3. フォルダとビューのリストの中にある (ByURL) をクリックして[OK]ボタンをクリックします。
  4. するとNotes URL列に"Notes:///レプリカID" といった感じでリストされたビューが開きます。
  5. Notes:///の直後に、メモしたレプリカIDが表示された文書のすべての行へチェックマークを付けます。
  6. Delete キーを押すと「O001 オブジェクト変数が設定されていません」とメッセージが表示されますが、気にせず[OK]ボタンをクリックします。
  7. F9 キーを押し「n 文書をデータベース ブックマーク (8) から削除しますか?」のようなプロンプトが表示されるので[はい]ボタンをクリックします。
この後、念のためLotus Notesを再起動します。

2009年12月7日月曜日

プリビューペインは名前をこうするとこうなるの


フレームセットを使ったアプリケーションで、ビューで選択した文書をプリビューウインドウで表示したい場合があります。

そんな時はプリビューペインとするフレームに付ける名前を "NotesPreview" とするべきです。

しないと後悔しますよ~

それはどうしてでしょうか?

プリビューペインの名前を "Preview" とか "DocumentFrame" のようにしていても、ビューで選択した文書はプリビューペインへ表示されます。

ただ、プリビューペインを開いたり閉じたりするコマンド @Command([ShowHidePreviewPane]) が利かない、Notesのメニューに「表示(V) - プリビューペイン(P)」が表示されないといった事態になるのです。

でも "NotesPreview" だったら大丈夫!

きちんとプリビューとして機能してくれますよ。

2009年12月5日土曜日

アクセス権があるのにアクセスできない理由


ドミノディレクトリのグループ文書へユーザーを登録した後、当該アプリケーションのアクセス制御リストの画面にあるボタン[有効なアクセス権]を使い、該当するグループの権限が追加したユーザーに付与されたことを確認しました。

その後ユーザーがアプリケーションリンクをクリックすると、アクセスする権限がないと叱られてしまい、アクセスできないことがあります。

どうしてでしょうか?

ドミノサーバーは、ユーザーが初めてサーバーにアクセスした時点でドミノディレクトリを参照し、ユーザーがどのグループに所属しているかを確認します。
その後ユーザーのセッションが切れるまで、アクセスの際にグループに所属するかどうかの確認はしないのです。

したがって、Notesを起動中にドミノディレクトリのグループのメンバーへ追加しても、そのユーザーは当該データベースへアクセスできないのです。

この場合、ユーザー側でログインしなおす、あるいは Lotus Notes を再起動することで一旦セッションが切れ、アクセスできるようになります。

管理者側で操作する場合、サーバーのコンソールから "Drop NotesID" あるいは "Drop All" を投入してセッションを切ることでアクセスできるようになります。

2009年12月2日水曜日

ビューを開いたとき最後に閉じた文書にフォーカスする


データベースを開いた時に、前回最後に開いた文書をビュー上で選択した(フォーカスした)状態にしたい場合、データベースのプロパティにて「最後に表示していたものを表示する」を有効にすれば「ほとんどの場合」問題ないのですが、最後に閉じた文書をフォーカスしたい、といわれることもあります。

細かいことは気にするな! といいたいところですがそこをグッとこらえて...

ビュー上で文書を選択するには NotesUIView クラスの SelectDocument メソッドを利用します。

最後に開いていた文書を示す値をプロフィール文書へ保存することにします。ここで「文書を示す値」としてユニバーサルID(UNID)を利用したいと思います。
プロフィール文書も複製されますから、このようにしておくことでレプリカを開いたときにも機能しますね。

最後に開いていた文書のUNIDをプロフィール文書へ保存するための式をフォームのイベント Queryclose へ記述します。

@If(@IsNewDoc; @Success; @SetProfileField("PrivateProfile"; "LastOpenDoc"; @Text(@DocumentUniqueID); @UserName))

フォームのイベント Queryclose へ記述した式や Lotus Script はフォームを閉じる前に処理されます。

プロフィール文書のフィールド LastOpenDoc へUNIDを保存します。ただし文書が保存されていない(@IsNewDoc の戻り値が True の)場合はUNIDがないため保存しません。

ビューを開いた後に最後に開いていた文書にフォーカスを当てる処理(Lotus Script)をビューのイベント Postopen へ記述します。

Sub Postopen(Source As Notesuiview)
 Dim db As NotesDatabase
 Dim prof As NotesDocument, target As NotesDocument

 Set db = Source.View.Parent
 Set prof = db.GetProfileDocument("PrivateProfile", db.Parent.UserName)
 If prof.HasItem("LastOpenDoc") Then
  Set target = db.GetDocumentByUNID(prof.GetItemValue("LastOpenDoc")(0))
  If Not (target Is Nothing) Then
   Call Source.SelectDocument(target)
  End If
 End If
End Sub

2009年11月30日月曜日

ビューの選択式で日付関数を使う


What's New のようなビューでは @Today や @Now といった日付関数を使うことがあります。

その場合はビューの利用頻度や文書数を考慮することが必要です。

特に使用頻度が高いDBで日付関数を使ったビューをトップ画面で必ず表示するといった場合、誰かがトップ画面を開くたびにサーバー側で行われるビューの再構築によりアクセス遅延が発生するなどパフォーマンスに大きく影響することがあります。

下手すると多くのユーザーへ一斉にデータベースリンクを付けたメールを送付した直後に「DBが開かない」といった電話がジャンジャン鳴り、こうなると不具合調査どころの騒ぎではありません...

ただしクレームが多いからといって便利なビューの使用をやめてしまうのは避けるべきです。

日付関数に変わる方法として @ToTime("Today") がよく例として挙げられます。

こうするとNotesを騙すことができるわけです。

ただこのように変更した場合、新規文書はビュー索引にきちんと追加されるのですが、期限を過ぎた文書がビュー索引から消えないといった不具合が発生することがあります。
この不具合を解消するには、ドミノディレクトリのプログラム文書を利用してビューを再構築するコマンド(updall -r -t )を発行するといった対応が必要になります。

下記のリンクには日付ビューを使う場合の考慮点などが分かりやすく掲載されています。

(参考)Lotus Notes の時刻/日付ビューのオプション
(参考)Notes ビュー索引の更新に関する基本
http://www-06.ibm.com/jp/domino04/lotus/support/faqs/faqs.nsf/all/731830

2009年11月29日日曜日

競合文書は一人で作れる


Notes/Dominoの特徴のひとつである競合文書は、複数のユーザーが同時期に同一文書を編集するなどの操作によって作成されます。

そもそもNotes/Dominoではレプリカといった機能がある関係からか厳密な排他制御は期待できません。云わば「お気楽な排他制御」ですね。
(というとお叱りをうけそうですが...)

そのため一つの文書を皆で編集するような仕様のアプリケーションの場合は「競合文書の削除」が定形業務になっていたりします。

でも、文書の作成者だけがその文書を編集できる仕様のアプリケーションでも競合文書ができることが時々あるのです。

実は次の3つの手順を踏むことで簡単に競合文書を作成できてしまいます。

1. ビューで競合文書にしたい文書を選択してEnterあるいはダブルクリックで開きます。これを表示モードのまま開いておきます。
2. ビューへ戻り、競合文書にしたい文書を選択した状態で Ctrl + E を押して編集モードで開き、保存して閉じます。
3. 1で開いておいた文書へ戻り、編集モードにして、編集、保存します。

3で保存するとき、メッセージ「編集しているときに文書のコピーが保存されています。競合文書として保存しますか?」が表示されますから[はい]をクリックします。

「知らない間に競合文書ができている」と言われることが時々ありますが、実は前出のメッセージがよくわからずに[はい]を押しちゃう人も多いようですね。

2009年11月27日金曜日

レプリカを一括で作るには


一度にたくさんのDBを検証環境や新規サーバーへ持っていきたい、といった要望はときどきあります。
このような時にNotesのレプリカを利用すると、サーバー稼働中でも比較的安全に持っていくことができます。

今回紹介するのは新規のローカルDBにフォームとアクションボタンをひとつ作るだけの簡単設計です。

フォームには、持っていきたいDBのファイルパスを入力するための複数値可のテキストフィールド "targetlist" と、下のLotus Scriptを実行するアクションボタンを作成します。
Dim ws As New NotesUIWorkspace
Dim ss As New NotesSession
Dim db As NotesDatabase, rep As NotesDatabase
Dim doc As NotesDocument

Set doc = ws.CurrentDocument.Document
If doc Is Nothing Then Exit Sub
On Error Resume Next

Forall target In doc.targetlist
 Set db = ss.GetDatabase("SERVERNAME/ORG/JP", target)
 Set rep = db.CreateReplica("", |sample\\| & target)
 If rep Is Nothing Then
  Print Cstr(target) & "...NG"
 Else
  Print Cstr(target) & "...OK" & " 「" & rep.Title & "」"
  Set rep = Nothing
 End If
End Forall
Print "処理終了"

上のLotus Scriptを実行すると、レプリカをローカルの "sample" フォルダ内へ作成します。

例として "targetlist" フィールドへ次のように入力した場合、
help\help65_admin.nsf
prod\phonebook.nsf
names.nsf

ローカルPCへ作成したレプリカのファイルパスは次のようになります。
sample\help\help65_admin.nsf
sample\prod\phonebook.nsf
sample\names.nsf


もしレプリカの作成先としてローカルPCの外付けHDDを指定したい場合、Notes\Dataフォルダへ予めディレクトリリンク"sample.dir"を作成するなどして、異なるドライブへ直接書き込めるよう工夫することもできますね。

この"sample.dir" とは「X:\SAMPLE (※Xドライブは外付けHDDのドライブレター)」とだけ書かれた(テキストエディタで編集できる)ファイルで、SAMPLE ディレクトリとして見せたい実態(ドライブとフォルダ)を指し示します。

なお、アクセス権が無いなどできちんとレプリカが作成されないとつまらないので、Domino Administrator でフルアクセスアドミニストレータ権限を有効にしておくのがお勧めです。

また、状況は Print でステータスバーに表示していますが、ステータスバーの情報をローカルのログ(log.nsf)へ残すようnotes.iniへ次の2行を加えておくと、後で確認しやすいと思います。
LogStatusBar=1
Console_Log_Enabled=1
状況の記録は Print でなくてもいいと思います。

2009年11月26日木曜日

Error locating a Domino Directory entry for certifier /O=ORG/C=JP


サーバー再起動を境に次のメッセージが昼夜を問わず日に数回、ログ(log.nsf)に記録されるようになりました。

Error locating a Domino Directory entry for certifier /O=ORG/C=JP: エントリが索引に見つかりません

メールとアプリケーションはドメインを分けて設置している環境であり、アプリケーションドメインではディレクトリアシスタンスを利用して二次ディレクトリとしてメールドメインのドミノディレクトリを指定してある。

前出のメッセージはアプリケーションのドメインにあるサーバーで記録された。

そこでメールドメインのドミノディレクトリにあるCertifier(Notes認証者)文書 "ORG/JP" をアプリケーションドメインのドミノディレクトリへコピーしてみたところ、それ以後記録されなくなった。

どうして再起動前まで発生しなかったのか、なにが起こったときにCertifier文書が参照されたのか、1日の利用者は数百人なのにどうして時々しか発生しなかったのか....謎はまだ残る...

2009年11月25日水曜日

テキストフィールドの32KB制限で文書が保存できない


フォーム上に表示用の計算結果として設置している非表示のフィールドがあり、その式に @DbColumn を利用してビューから値のリストを抽出するアプリケーションがありました。

@DbColumn により抽出するテキストのサイズは、設計当初は数KB程度と見積もっていたのですが、とうとうオーバーフローしてしまったようで、フォームを保存するときに次のメッセージが表示され保存できなくなったと報告がありました。

Only plain text can be used in this type of field.

(利用者が外国人のため英語です)

上記メッセージにOKボタンを押してもどのフィールドにもカーソルが移動しないのでドキドキしましたが、すべての非表示フィールドを表示してみたところ、テキストサイズがやたら大きいのでメモ帳へコピペして保存したところサイズが32KB付近まで膨れ上がっていました。

そこで @DbColumn の式を削除して "" としてみたところ、メッセージが表示されずに保存できるようになりました。

上記はテキストフィールドのサイズ制限に起因する不具合でしたが、メッセージがとてもわかりづらかったです。


ところで @DbColumn や @DbLookup はフォームを開くときに式を評価するため、パフォーマンスが悪くなりやすいのをご存じでしょうか。

このフォームにはコード選択用のダイアログボックス・フィールドが10個あり、どのフィールドも同じキーワードリストから1つだけ選択させるのですが、このキーワードリストの値を導き出すための式として前出の表示用の計算結果フィールド名を設定しています。

こうすることで @DbColumn を多用しない工夫をしていました。

この不具合が発覚したため、結局のところ10個の @DbColumn を書くハメになりました。

トホホ

2009年11月24日火曜日

半角英数字以外の文字が入力されたらエラーにする

テキストフィールドにおいて半角英数字以外をエラーとする場合、フィールドの「入力の確認」へ次の式を設定します。

@If(@Matches(@ThisValue; "+{a-zA-Z0-9}"); @Success; @Failure("エラーです"))

半角英数字と半角記号の場合半角カナを含む場合などはこちらを参照してください。

2009年11月19日木曜日

懇談室よいとこ、一度はおいで

Lotus Notes には R4.6J が出る少し前から関わっていますので、もう12年も触っていることになります。

この12年で得られたノウハウが少しでも誰かの役に立てばいいなと思うし、やっぱりわかんない事は誰かに相談したいと考えて、ときどき訪問するサイトがあります。

ドミノ懇談室

日々の投稿数はさびしい感じがしなくもないですが、システムが安定していることの裏がえしですかね。

手順と質問の仕方を誤らなければ多くの経験者から的確なアドバイスがもらえます。
ただし「すぐに」という保証はありません。
ですが困っているときなどはすごく救われた気分になれます。

過去の質問と返答を見ると事象が似ているものがあったりするので、質問する前には必ず検索したいですね。

質問する場合のコツは、とにかく詳しく書くこと。
大雑把な質問だと期待した回答がすぐに得られないかもしれません。

事の経緯や現象、何がしたいか、どうなってほしいか、どんなコードを書いたらどんなエラーがどこに出たとか、どんなキーワードで検索したといったことでもいいと思います。

返答する側が答えやすいよう質問する側も工夫したいですね。