Usability - Productivity - Business - The web - Singapore & Twins

Reader Fields - extreme Edition (update)

Reader fields are "haunting" me. Recently I was asked to provide a solution for a requirement to add a very large number of entries to a reader field. A number that firmly exceeds the storage capability of a reader field (still 32k after all these years). My first instinct was to look for alternatives like using a few group names. However the alleged business case is the exclusion of a few people from a large group (somehow: the whole department can read a document except the person who has birthday since it is a party-planning application). This was one of the cases where I was wishing for a new field-type: NoReaderField (and for that sake NoAuthorField).
Well there is always the next version. Until then I created a small class, that allows you to add, remove and query names in a "virtual" reader field. Instead of reading and writing into a Notes item the developers call a method in a class (addEntry(newName as String) or addEntries(newNames as Variant) (an Array)) to interact with the reader field. The "saveField" method then distributes the values onto one or more reader fields. I don't made it too scientific and counted bytes, but rather count number of names. Splitting the reader names across multipe fields requires adjustments to your views. Something like Reader1:Reader2:Reader3:....:Readerx. We haven't tested the impact of that on performance. Once I have data on that I'll provide an update.

Update: Turns out the class was a bad idea. There is a hard stop for the total size of all combined Reader fields in a document. So the alternative is to use an approach similar to the NoReader field. I consulted with the master and discuss alternatives.

Have a look at the code yourself:
Option Public Option Declare '/**' * ' * FieldAccess is a universal class to manage large amount of values put into text fields ' * (text, names, readers, authors etc. ' * it manages these large amount by splitting reader names into multiple fields to overcome ' * the 32k size limit. New fields are created as needed. ' * Good for 62500 entries ' * ' * v0.1 2009-03-20 : notessensei@sg.ibm.com '**/ '// Important: This class doesn't contain production quality error handling, you need to add '// that for production use. Use OpenLog for best results! Public Class FieldAccess Private AccessFieldName As String 'The base name for the Reader field Private maxFieldEntries As Integer 'The maximum number of entries per reader field Private maxNumOfFields As Integer 'Limit the number of fields, to avoid mising data in views Private maxTotal As Integer 'The maximum number of names in total Private curTotal As Integer 'The current number of entries Private curFieldMembers List As String 'The list of current Reader entries Private fieldCount As Integer 'The current number of fields Private initialized As Boolean 'Has the list of names been initialized Private doc As NotesDocument 'The current document Private fieldType As Integer 'Text, names, reader, author Sub new (curDoc As NotesDocument ) Set Me .doc = curDoc Me .AccessFieldName = "Field" Me .maxFieldEntries = 500 Me .maxNumOfFields = 10 'Adjust as needed Me .initialized = False Me .fieldType = 0 'No special type End Sub 'Need to set the field Name Public Property Set fieldName As String If Me .AccessFieldName < > fieldName Then Me .AccessFieldName = fieldName Me .initialized = False End If End Property Public Property Get FieldName As String fieldName = Me .AccessFieldName End Property 'How many entries are in the names field right now Public Property Get count As Integer If Not Me .initialized Then Me .initializeFields End If count = Me .curTotal End Property 'Add a entry to the value list Public Sub addEntry (entryToAdd As String ) If Not Me .initialized Then Me .initializeFields End If If Not Iselement ( Me .curFieldMembers (entryToAdd ) ) Then If Me .curTotal = Me .maxFieldEntries * Me .maxNumOfFields Then Error 8000 'We throw an error Else Me .curFieldMembers (entryToAdd ) = entryToAdd Me .curTotal = Me .curTotal + 1 End If End If End Sub 'Bulk adding of entries. Suitable to pull in entries from getItemValues
Public Sub addEntries (entriesToAdd As Variant ) Dim oneEntryToAdd As String If Not Isarray (entriesToAdd ) Then Error 8001 'We won't process something that's not an array End If Forall curEntry In entriesToAdd oneEntryToAdd = curEntry Call Me .addEntry (oneEntryToAdd ) End Forall End Sub 'Entry to remove. Is removed from thefields if exist. If the entry 'doesn't exist no error is raised Public Sub removeEntry (entryToRemove As String ) If Not Me .initialized Then Me .initializeFields End If If Iselement ( Me .curFieldMembers (entryToRemove ) ) Then Erase Me .curFieldMembers (entryToRemove ) Me .curTotal = Me .curTotal - 1 End If End Sub 'Bulk removal of Entries. Suitable to pull in values from getItemValues Public Sub removeEntries (entriesToRemove As Variant ) Dim oneEntryToRemove As String If Not Isarray (entriesToRemove ) Then Error 8001 'We won't process something that's not an array End If Forall curEntry In entriesToRemove oneEntryToRemove = curEntry Call Me .removeEntry (oneEntryToRemove ) End Forall End Sub 'Checks the reader fields for the existance of and entry Public Function containsValue (valueToLookup As String ) As Integer If Not Me .initialized Then Me .initializeFields End If containsValue = Iselement ( Me .curFieldMembers (valueToLookup ) ) End Function 'Saves the changes in fields back to the document Public Sub saveField Dim curFieldCount As Integer Dim tmpCount As Integer Dim tmpTotal As Integer Dim tmpMax As Integer Dim allEntries ( ) As String 'We save the names in chunks of maxFieldEntries 'into Names fields and remove fields we don't need anymore curFieldCount = 1 tmpTotal = 0 tmpCount = 0 'Size the array to fit either all entries or the max# of entries If Me .maxFieldEntries < Me .curTotal Then Redim allEntries ( Me .maxFieldEntries ) Else Redim allEntries ( Me .curTotal ) End If 'Now we go through the names and put them into field buckets Forall curEntry In Me .curFieldMembers If tmpCount > Ubound (allEntries ) Then 'Save the current field Call Me .SaveToField (allEntries ,curFieldCount ) 'Reset the array '//TODO: Check if this has an off-bye-one-error! If Me .maxFieldEntries > ( Me .curTotal - tmpTotal ) Then Redim allEntries ( Me .curTotal - tmpTotal ) Else Redim allEntries ( Me .maxFieldEntries ) End If tmpCount = 0 curFieldCount = curFieldCount + 1 End If tmpTotal = tmpTotal + 1 allEntries (tmpCount ) = curEntry 'Get to the next tmpCount = tmpCount +1 End Forall 'We need to save the last batch too Call Me .SaveToField (allEntries ,curFieldCount ) 'Remove field we might not need anymore If curFieldCount < Me .fieldCount Then Call Me .RemoveFields (curFieldCount +1 , Me .fieldCount ) Me .fieldCount = curFieldCount End If End Sub 'Saves one field Private Sub SaveToField (curVals As Variant , fieldCountOffset As Integer ) Dim fieldName As String Dim newField As NotesItem fieldName = Me .AccessFieldName + "_" + Trim ( Cstr (fieldCountOffset ) ) If doc .HasItem (fieldName ) Then Call doc .RemoveItem (fieldName ) End If Set newField = New NotesItem (doc ,fieldName ,curVals , Me .FieldType ) End Sub 'Removes fields we don't need Private Sub removeFields (startCount As Integer , endCount As Integer ) Dim fieldName As String Dim newField As NotesItem Dim i As Integer For i = startCount To endCount fieldName = Me .AccessFieldName + "_" + Trim ( Cstr (i ) ) If doc .HasItem (fieldName ) Then Call doc .RemoveItem (fieldName ) End If Next End Sub 'Initialize the fields Private Sub initializeFields Dim namesField As NotesItem Dim i As Integer Dim curEntry As String Dim curVals As Variant If doc .HasItem ( Me .AccessFieldName ) Then Me .curTotal = 0 '//TODO:Full check for missing 1st field i = 1 Do While True curEntry = Me .AccessFieldName + "\_" + Trim ( Cstr (i ) ) If Not doc .HasItem (curEntry ) Then i = i -1 'We overcounted one, so we step back Exit Do End If curVals = doc .GetItemValue (curEntry ) 'Save the names into a list for processing Forall member In curVals If Not Iselement (curFieldMembers (member ) ) Then curFieldMembers (member ) = member Me .curTotal = Me .curTotal + 1 End If End Forall i = i + 1 Loop Me .fieldCount = i Else Me .fieldCount = 0 Me .curTotal = 0 End If Me .initialized = True End Sub End Class '/** ' _ ' _ ReaderAccess is a universal class to manage large amount of reader names in a document '**/ '// The only difference to FieldAccess is the FieldName and the FieldType Public Class ReaderAccess As FieldAccess Sub New (curDoc As NotesDocument ) 'The base class runs first, so we only need to add the changes Me .AccessFieldName = "DocReaders" Me .fieldType = READERS 'Set type to READER End Sub End Class
This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

Posted by on 15 April 2009 | Comments (2) | categories: Show-N-Tell Thursday


  1. posted by Srinivas Kotaru on Wednesday 15 April 2009 AD:
    Wow. Great piece of code. Thanks a lot.
  2. posted by Kevin Pettitt on Wednesday 15 April 2009 AD:
    Nice one Stephan.

    I'm guessing Reader fields have to be "Summary" fields, but if not you could double the size of the field contents to 64K. No biggie since once you're building multiples on the fly the number probably isn't that important. If performance sucks though it might be worth investigating. Also, if that direction proves useful, you have to make sure the corresponding form doesn't have the Reader_1, etc. fields on it or the summary flag will be reapplied if saved in the UI (or at least it'll try).