For a couple of applications, I’ve needed a way to store values specific to the combination of a user and workstation. These are personal settings, in other words, but the same person may need different settings if they use the application on a different workstation.
This would mostly be local filepaths, things like the last folder the user selected for a particular file-open prompt, or the path of an external tool the user has to launch in a given situation. A list of recently accessed files. These would be different on different workstations.
Implementation strategy and alternatives
You could use environment variables for some of this, and that might be the answer in some situations. But there’s a limit to the amount of data you can conveniently store that way, and you have to worry about name conflicts, especially between different copies of the same application. Also, if it’s a shared workstation, you may want to allow for different values per user.
It makes sense to store these settings in the application itself. Profile documents are nice for this because they were designed for precisely that type of data access. They can be accessed by macro code as well as LotusScript. They’re aggressively cached for best performance, to the extent different LotusScript execution environments that have the same profile in memory, can see each others’ changes in real time. There’s only ever one copy in memory at a time in a given Notes process.
Personal profile documents are accessed by a key value and the username. The username is also an Authors item in the profile, allowing users with Author access to create and modify one. So the key value needs to be the part unique to the workstation.
Profiles and forms
To provide a unique key value per workstation, I assign each workstation a unique ID stored in an environment variable. Every application can use the same workstation ID INI setting — it’s fine if profiles in other applications have the same key.
The workstation ID, in my code, is stored in the environment variable LPD_ID. To allow use in macro code, it’s not a system variable. It’s generated with the @Unique function just once for the workstation, by whichever application first wants to use it and finds it missing. For LotusScript code, I’ll provide a script library to manage it. For macro language, it can be accessed as follows:
@GetProfileField(@Environment("LPD_ID"); "itemname"; @UserName)
“” (empty string) isn’t a valid value for the profile name argument. For formulas like this to work without error, they either have to check whether the ID has been assigned (which this one doesn’t do), or we have to make certain the ID is assigned before trying to evaluate the formula. This can be done in the Initialize event of the Database Script.
If you find this website helpful and want to give back, may I suggest buying, reading, and reviewing one of my excellent books? More are coming soon!
If you want email whenever there’s a new post, you can subscribe to the email list, which is 100% private and used only to send you information about stuff on this site.
Specification
The implementation is a LotusScript library, LocalProfileDoc, which declares and initializes a global object, GLocalProfile, of type LocalProfileDoc, with the following properties and methods:
- Sub Init: intended to be called from the Initialize event of the Database Script, makes sure the workstation ID is defined in an INI setting.
- WorkstationID (String, read-only): the ID of this workstation, generated via @Unique function then stored in an INI setting.
- Profile (NotesDocument, read-only): the user/workstation profile document.
- isDirty As Boolean (read/write): Tells whether any fields of the profile have been modified. If True, the profile is automatically saved on exit. If you use Profile property and change item values in the document, you must explicitly set this property.
- Function GetValue(itemname) As Variant: Returns the value of a profile field as a scalar. If the field is multivalued, returns the first value.
- Function GetValue(itemname) As Variant: Returns the value of a profile field as an array, similar to NotesDocument.GetItemValue method.
- Sub SetValue(itemname, value): Assigns a profile field, if it doesn’t already have the specified value. The profile isn’t immediately saved. isDirty property shows whether the profile needs saving.
- Sub Save: Save pending profile changes, if any. If isDirty is false, does nothing.
- Function EditInDialog(formname$, title$, Autohorzfit As Boolean, Autovertfit As Boolean, Nocancel As Boolean, Nookcancel As Boolean, Okcancelatbottom As Boolean) As Boolean: Edit the profile document in a dialogbox using the specified form. If the user presses OK their changes are saved. Function returns True if changes are saved.
Source code
The implementation is a single script library, as follows:
%REM Library LocalProfileDoc This library allows for the creation of a Notes workstation-specific personal profile document. This can be used to store personal preferences limited to the current workstation. It's appropriate for such things as last-used filepaths for file open dialogs, paths of external tools -- anything involving a local filepath, or that can potentially be different on different workstations. To use this, have the database script contain the following code: Use "LocalProfileDoc" Sub Initialize GLocalProfile.Init ' make sure the workstation has an ID End Sub Where you need to use or set values in the local profile, use this library and the GLocalProfile object, or in macro language use a formula such as: @GetProfileField(@Environment("LPD_ID"); "itemname"; @UserName) © 2022 Andre Guirard Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. %END REM
Option Public Option Declare' Global used to access user/workstation profile document.
Public GLocalProfile As LocalProfileDoc' INI setting containing unique workstation ID for user/workstation local profile docs.
Public Const LOCALPROFILE_ID_INI = "LPD_ID"%REM Class LocalProfileDoc By Andre Guirard Description: Manages a profile document specific to this workstation+user combination. Constructor: Do not create, use existing global GLocalProfile. %END REM
Class LocalProfileDoc Private z_workstationID As String Private z_db As NotesDatabase Private z_docPro As NotesDocument Public isDirty As Boolean%REM Sub Init Description: Make sure there's a workstation ID assigned. %END REM
Function WorkstationID As String If z_workstationID = "" Then Dim ses As New NotesSession, tmp z_workstationID = ses.Getenvironmentstring(LOCALPROFILE_ID_INI, False) If z_workstationID = "" Then tmp = Evaluate(|@Right(@Unique; "-")|) z_workstationID = tmp(0) Call ses.Setenvironmentvar(LOCALPROFILE_ID_INI, z_workstationID, False) End If End If WorkstationID = z_workstationID End Function%REM Property Profile (read) Description: Get the profile document. If you change field values in the profile, you must set GLocalProfile.Dirty = True to make sure they're saved. %END REM
Public Property Get Profile As NotesDocument If z_docPro Is Nothing Then Dim ses As New NotesSession If z_db Is Nothing Then Set z_db = ses.Currentdatabase End If Set z_docPro = z_db.Getprofiledocument(WorkstationID, ses.Username) End If Set Profile = z_docPro End Property%REM Function GetValue Description: Retrieve a single item value from the profile. Arguments: itemname: the name of the item to retrieve Returns: a SINGLE string, number, or date. If the item is multivalued use GetArray %END REM
Function GetValue(ByVal itemname$) As Variant Dim tmp tmp = Profile.Getitemvalue(Itemname) If IsArray(tmp) Then GetValue = tmp(LBound(tmp)) Else GetValue = tmp End Function%REM Function GetArray Description: Retrieve a multivalued item value from the profile. Returns: an array even if it's a single item -- like NotesDocument.GetItemValue %END REM
Function GetArray(ByVal itemname$) As Variant Dim tmp tmp = Profile.Getitemvalue(Itemname) If IsArray(tmp) Then GetArray = tmp Else GetArray = tmp End Function%REM Function fieldValsCompare Description: Compare two values read from document items to determine which is larger. Similar to StrComp. If one is an array and one is a scalar, the scalar is treated as a one-element array. The values may be strings, dates, or numbers, but they should be of the same type. Arguments: val1, val2: the values to compare -- may be arrays or scalars. Return value: -1 if val2 is "larger," 1 if val1 is larger, 0 if equal. %END REM
Private Function fieldValsCompare(val1, val2, ByVal flags%) As Integer On Error GoTo oops fieldValsCompare = 1' if compare fails, always return 1.
If IsArray(val1) Then If IsArray(val2) Then If UBound(val1) = UBound(val2) Then If DataType(val1(0)) = DataType(val2(0)) Then Dim i%, tmp% If DataType(val1(0)) = 8 Then For i = 0 To UBound(val1) tmp = StrComp(val1(i), val2(i), flags) If tmp <> 0 Then Exit For Next Else For i = 0 To UBound(val1) tmp = Sgn(val1(i)-val2(i)) If tmp <> 0 Then Exit For Next End If fieldValsCompare = tmp End If End If End If Else If Not IsArray(val2) Then If DataType(val1) = DataType(val2) Then If DataType(val1) = 8 Then fieldValsCompare = StrCompare(val1, val2, flags) Else fieldValsCompare = Sgn(val1-val2) End If End If End If End If oops: Exit Function' returning not-equal
End Function%REM Sub SetValue Description: Set the value of a profile item. Arguments: %END REM
Sub SetValue(ByVal itemname$, value) Dim curval curval = Profile.Getitemvalue(Itemname) If fieldValsCompare(value, curval, 0) then Call Profile.Replaceitemvalue(Itemname, value) isDirty = True End If End Sub%REM Sub Save Description: Save any pending changes to profile document. Arguments: %END REM
Sub Save If isDirty Then Call Profile.Save(True, False, True) isDirty = false End If End Sub%REM Sub Init Description: Make sure there's a workstation ID assigned to this workstation. Arguments: %END REM
Sub Init Dim tmp$ tmp = WorkstationID End Sub%REM Function EditInDialog Description: Edit the profile document in a dialog box. The changes are saved to the profile. Arguments: formname: What form to use in the dialog (since the profile key doesn't correspond to a form). other arguments as for NotesUIWorkspace.Dialogbox - some are omitted because e.g. nobody uses layout regions. Returns: True if user clicks OK. %END REM
Function EditInDialog(ByVal formname$, ByVal title$, ByVal Autohorzfit As Boolean, ByVal Autovertfit As Boolean, _ ByVal Nocancel As Boolean, ByVal Nookcancel As Boolean, ByVal Okcancelatbottom As Boolean) As Boolean Dim wksp As New NotesUIWorkspace Save' any pending changes
EditInDialog = wksp.Dialogbox(Formname, Autohorzfit, Autovertfit, Nocancel, false, false, false, Title, Profile, (Autohorzfit Or Autovertfit), Nookcancel, Okcancelatbottom) If EditInDialog Then isDirty = True Save End If End Function Sub Delete Save End Sub End Class Sub Initialize Set GLocalProfile = New LocalProfileDoc End Sub
Final notes
There’s not a straightforward way to edit the profile in a full-screen window. You can, but it would require a specially designed form with special code on it to load and save the profile fields rather than allowing a normal document compose and save. Left as an exercise.
This tool’s value is enhanced if there’s also a library of UI code to make use of it. For instance, the Image Catalog application I’m working on for OpenNTF publication, contains a “file open dialog” UI function that takes a Situation argument, telling which file-open dialog it is so we can default to the last-used folder specific to that situation.