I have been working on an application that has a rather complicated form that includes three embedded subforms. QA testing on unrelated enhancements identified performance issues with this form. The current production release would load documents with this form in 3 seconds. The new QA build would load documents with this same form in 30 seconds. After as series of tests it was found that the subforms were causing the slow perfromance (e.g. taking the fields on the subforms and pasting them into the form made it run much faster). I did some research and found the following blog suggesting that adding a Print "" statement to the initialize module of the subform. Sure enough when I did this, my documents were again loading in 3 seconds. Further testing showed that almost any LotusScript code could be added and have the same effect. I settled on adding a Stop statement as this seemed to have less impact on the application. Subforms are known to have an exponential impact on performance when the number used starts to get large. But having seen this with just three subforms I am now tempted to add a Stop statement in the Initialize module of ALL my subforms from now on to improve the performance. It is somewhat ironic that Stop means Go!!
|
Improving The Performance Of Subforms
|
One of the things I find I am often doing in creating business logic for an application is determining if the curtrent user has a specific role. I have just added the following HasRoles property to the DominoDatabase class inside the .DOmino Framework as a way of quickly if a user has a specific role:- '/**
' * Determine if specific user has a particular role for this database
' */
Property Get HasRole(pUserName As Variant,pRole As String) As Boolean
Dim Username As String
Dim UserRoles As Variant
Try:
On Error Goto Catch
If(iDB Is Nothing) Then Exit Property
Select Case Typename(pUsername)
Case "STRING"
If (pUserName = "") Then UserName$ = Session.UserName$ Else UserName$ = Cstr(pUserName)
Case "NOTESITEM"
UserName = Cstr(pUserName.Values(0))
Case Else
UserName$ = Session.UserName$
End Select
UserRoles = iDB.QueryAccessRoles(UserName$)
Forall UserRole In UserRoles
If Cstr(UserRole) = pRole Then
HasRole = True
Exit Property
End If
End Forall
HasRole = False
Exit Property
Catch:
Stop
Call ReportError()
Exit Property
End Property
|
DominoDatabase.HasRole Property
|
The 0.6 Beta of the .DominoFramework already included a LotusScript implementation of the hypothetical @GetFile function. This provides a shortuct to request the operating system to prompt the user to select a file from the operating system using an undocument option of the NotesUIWorkspace.Prompt method:- '/**
' * Prompts user to select a OS file using undocumented UIW.Prompt option
' *
' * @author Peter Presnell
' * @param Default File Initial file name
' * @return Name of selected file (empty if cancel selected)
' */
Function atfGetFile(DefaultFile As String) As String
Dim Title As String
If DB Is Nothing Then Title$ = ".Domino Framework" Else Title$ = DB.Title$
atfGetFile$ = Cstr(UIW.Prompt(12,Title$,"",DefaultFile$))
End Function The next release of the .Domino Framework will also provide an implementation of the hypothetical @GetFileContent function. This function can either take a filename as a parameter or prompt the user for a filename and then returns the contants of that filename in a NotesStream. Again it acts as a shortuct way of getting the contents of a file without the need to repeat the LotusScript code to do this. '/**
' * Reads the contents of a nominated file
' *
' * @author Peter Presnell
' * @param Source NAme of file to be read (Where non is provided the user is prompted to provide the file name)
' * @returns NotesSTream containing the contents of the file
' */
Function atfGetFileContent(Source As Variant) As NotesStream
Dim FileName As String ' Name of file whose content is to be read
Dim FileNum As Integer ' File number
Dim Text As String ' Line of text read from file
Try:
On Error Goto Catch
If Session.IsOnServer Then Exit Function
Set atfGetFileContent = Session.CreateStream()
Select Case Typename(Source)
Case "STRING"
If FileName$ = "" Then FileName$ = atfGetFile("") Else FileName$ = Cstr(Source)
Case Else
FileName$ = atfGetFile("")
End Select
If (FileName$ = "") Then Exit Function
FileNum% = Freefile()
Open FileName$ For Input As FileNum%
Do While Not Eof(FileNum%)
Line Input #FileNum%, Text$
Call atfGetFileContent.WriteText(Text$,EOL_CRLF)
Loop
atfGetFileContent.Position& = 0
Goto Finally
Catch:
Stop
Call ReportError()
Resume Finally
Finally:
If (FileNum% <> 0) Then Close FileNum%
End Function
|
@GetFile & @GetFileContents
|
The next release of the .Domino Framework will contain two new properties for the DominoDocument class. The XMLStream property will provide an XML/DXL representation of the document as a Notes Stream (2GB limit). The XML property will provide a XML/DXL representation of the document as a String (64K limit). Because the DominoDocument design class extends the DominoDocument class these two properties will also provide XML representations of Design documents. Note: Notes does not yet provide full fidelity for representing Notes document as DXL. The above properties carry these same limitations. '/**
' * XML (DXL) Representation of document
' */
Property Get XML As String
Dim XMLStream As NotesStream
If (iDocument Is Nothing) Then Exit Property
Set XMLStream = Me.XMLStream
XMLStream.Position = 0
If (XMLStream.Bytes < 2^16) Then XML$ = XMLStream.ReadText()
End Property
'/**
' * XML (DXL) Representation of document in the format of a NotesStream
' */
Property Get XMLStream As NotesStream
Dim Exporter As NotesDXLExporter
Dim XMLInputParser As NotesXMLProcessor
Try:
On Error Goto Catch
If (iDocument Is Nothing) Then Exit Property
Set XMLStream = Session.CreateStream()
Set Exporter = Session.CreateDXLExporter(iDocument,XMLStream)
Call Exporter.Process()
Exit Property
Catch:
Stop
Exit Property
End Property
|
DominoDocument.XML
|
In a recent blog I documented a new DominoBES class that has been added to the .Domino Framework. Due to a current limitation with LotusScript , this class had to be written in Java. After doing some experimentation I found a way of abstracting the Java Implementation of the class by "extending" the Java class with a LotusScript class. In theory this allows me to not only publish a Java class as a LotusScript class but to extend the Java class with additional methods/properties developed using LotusScript. Why bother? For one thing it is typically not a good idea to mix and match programming languages in an application as code maintenance then requires a developer with multiple programming language skills. For another, at the site where I am making use of this class there are about 30 full-time Notes developers and several hundred part-time Notes "developers", almost none of whom program in Java. By choice I am developing the .Domino Framework using LotusScript wherever I can and using Java where it makes sense to do so because the pool of Note LotusScript developers greatly exceeds the pool of Notes Java developers. The following is the code that can be found in the latest beta version of the .Domino Framework for extending the DominoBES class. (Note: I believe both the LotusScript and Java classes can have the same name)
Option Public Option Declare Uselsx "*javacon" Use "base.Domino.MailServices.BES.java" Use "Domino.Applications"
' C L A S S b a s e D O M I N O B E S ___________________________________________________________________________ C L A S S '/** ' * Communication with BES Server '* ' * @author Peter Presnell ' */ Class baseDominoBES As DominoBaseClass Private JavaSession As JAVASESSION Private JavaClass As JAVACLASS Private DominoBESJava As JavaObject Private MDSHost As String Private MDSPort As Integer '/** ' * Constructor ' */ Sub New(),DominoBaseClass("") Dim ApplicationSettings As New DominoApplicationSettings(Nothing) Set JavaSession = New JAVASESSION() Set JavaClass = JavaSession.GetClass("DominoBES") Set DominoBESJava = JavaClass.CreateObject() MDSHost$ = ApplicationSettings.BESServer$ MDSPort% = ApplicationSettings.BESPort% End Sub '/** ' * Push a browser channel to a Blackberry device. Displays one of two icons on application menu that link to a designated URL opened in teh BB browser. ' * ' * @param Email The email name of the blackberry user ' * @param PushURL The URL to be launched when the icon is selected ' * @param UnreadIconURL URL for the icon to be displayed until the BB user selects the icon ' * @param ReadiconURL URL for the icon to be displayed after te BB user selects the icon ' * @param PushID A unique ID to represent this channel on the BB device ' * @param PushTitle Text displayed along with the icons for the channel ' */ Sub BrowserChannelPush(Email As String,PushURL As String, UnreadIconURL As String, ReadIconURL As String, PushID As String, PushTitle As String) Call DominoBEsJava.browserChannelPush(MDSHost$,MDSPort%,Email$,PushURL$,UnreadIconURL,ReadIconURL,PushID$,PushTitle$) End Sub End Class
|
Extending a Java Class With LotusScript
|
To view the source (HTML) code from your Blackberry browser use the following key sequence:- <Alt><R><B><V><S>
|
Viewing HTML Source on Blackberry Device
|
The 0.6 Beta of the .Domino Framework was published on OpenNTF on the weekend. One of the last minute additions in this release is the abillity to access the XSLT/Skins feature of the .Domino Framework from within the Inspector tool. The significance of this is that this the Inspector tool can be invoked against ANY Notes database. So this tool now allows developers to apply XSLT transformations to design elements without the need to include any additional code in the applications themselves. All the code (and XSLT) is held in the .Domino Framework database. e.g. I frequently take on development for existing Notes applications in which the fonts used in forms an/views are inconsistent. Using Inspector I can apply an XSLT from the .Domino Framework to all view/forms that change all fonts to Default Sans Serif saving me hours of manual (and boring) coding.
|
XSLT Transformation Added To Inspector
|
Earlier today Tim Tripcomy wrote a great article about Nexus, a new tool he has developed to allow databases to be assigned new replica ids. After reading the article I decided to finally get around to adding a Read/Write ReplicaID property to the .Domino Framework's DominoDatabase class based upon this code. (Thanks Tim, I remember seeing the breaking par code a long time ago and forgot where I had seen it). I will also be updating the Inspector tool included with the fraemwork so that the replicaid becomes editable. The code is as follows:- '/**
' * Replica ID of database (NotesDatabase property is read-only)
' */
Property Get ReplicaID As String
If (iDB Is Nothing) Then Exit Property
ReplicaID$ = iDB.ReplicaID$
End Property
Property Set ReplicaID As String
Dim DB_Path As String*256
Dim hDB As Long
Dim ReplicaInfo As DBReplicaInfo
If (Len(ReplicaID$) < 16 Or Len(ReplicaID$) > 17) Then Exit Property ' ReplicaID must be 16 characters with optional ":" in the middle (as used by @formulae)
If iDB Is Nothing Then Exit Property
Call OSPathNetConstruct(0, iDB.Server$, iDB.FilePath$, DB_Path)
API_Return_Code = NSFDbOpen(DB_Path, hDb)
If (API_Return_Code <> 0) Then Exit Property
API_Return_Code = NSFDbReplicaInfoGet(hDb, ReplicaInfo)
If (API_Return_Code = 0) Then
ReplicaInfo.ReplicaID.Byte(0) = Val("&H"+Right(ReplicaID$,8))
ReplicaInfo.ReplicaID.Byte(1) = Val("&H" + Left(ReplicaID$,8))
API_Return_Code = NSFDbReplicaInfoSet(hDb, ReplicaInfo)
End If
API_Return_Code = NSFDbClose(hDb)
End Property
|
New DominoDatabase.ReplicaID Property
|
The @WebDBName function provides a quick way to get a reference to the current database in a format that can be used as a URL. I am working on an application that needed much the same thing except I needed a link to another Notes database. I decided to create a LS equivalent of the @WebDBName function that takes a single parameter. This parameter can be Nothing (current database), String (filepath of database) or a NotesDatabase object and returns the URL for that database. This function will be added to the next release of the Domino.@Functions namespace (possibly released on OpenNTF this weekend). '/**
' * Returns the name of a Notes database encoded for URL inclusion.
' *
' * @author Peter Presnell
' * @param Source Database path - Nothing = current database
' */
Function atfWebDBName(Source As Variant) As String
Dim Filepath As String
Select Case Typename(Source)
Case "NOTESDATABASE"
FilePath$ = Source.FilePath$
Case "STRING"
FilePath$ = Cstr(Source)
Case "OBJECT"
FilePath$ = Session.CurrentDatabase.FilePath$
End Select
atfWebDBName$ = atfReplaceSubstring(FilePath$,Split("\\: ",":"),Split("/:%20",":"))
End Function Note: In some cases an alternative to the above could be to use the NotesDatabase.HttpURL property.
The code for the atfReplaceSubstring (LS version of @ReplaceSubstring) is as follows:- '/**
' * Replaces specific words or phrases in a string with new words or phrases that you specify
' *
' * @author Peter Presnell
' * @param Source he string whose contents you want to modify.
' * @param Search A list containing the words or phrases that you want to replace.
' * @param ReplaceTo A list containing the replacement words or phrases.
' * @return The sourceList, with any values from fromList replaced by the corresponding value in toList.
' * If none of the values in fromList matched the values in sourceList, then sourceList is returned unaltered.
' */
Function atfReplaceSubstring(Source As Variant, Search As Variant, ReplaceTo As Variant) As Variant
Dim Results As Variant ' Interim results
Dim Index As Long
Dim ReplaceToItem As Variant ' Replacement value for match
Try:
On Error Goto Catch
If Isarray(Source) Then
Redim Results(Lbound(Source) To Ubound(Source))
For Index& = Lbound(Source) To Ubound(Source)
Results(Index&) = atfReplaceSubstring(Source(Index&),Search,ReplaceTo)
Next Index&
atfReplaceSubstring = Results
Else
atfReplaceSubstring = Source
If Isarray(Search) Then
For Index& = Lbound(Search) To Ubound(Search)
If Isarray(ReplaceTo) Then
If Index& > Ubound(ReplaceTo) Then
ReplaceToItem = ReplaceTo(Ubound(ReplaceTo))
Else
ReplaceToItem = ReplaceTo(Index&)
End If
Else
ReplaceToItem = ReplaceTo
End If
atfReplaceSubstring = atfReplaceSubstring(atfReplaceSubstring,Search(Index&),ReplaceToItem)
Next Index&
Else
' Covert all parameters to strings
If Isarray(ReplaceTo) Then
ReplaceToItem = Cstr(ReplaceTo(0))
Else
ReplaceToItem = Cstr(ReplaceTo)
End If
Source = Cstr(Source)
Search = Cstr(Search)
' Locate each occurence of search item and replace with replacement item
If Search <> ReplaceToItem Then
While Instr(atfReplaceSubstring, Search) > 0
atfReplaceSubstring = Left$(atfReplaceSubstring, Instr(atfReplaceSubstring, Search) - 1) + ReplaceToItem +_
Right$(atfReplaceSubstring, Len(atfReplaceSubstring) - Instr(atfReplaceSubstring, Search) - Len(Search) + 1)
Wend
End If
End If
End If
Exit Function
Catch:
Stop
Call ReportError
Exit Function
End Function
|
LS Version @WebDBName
|
I am now in the process of extending the .Domino Framerwork to support the publishing of Notes applications on a Blackberry client. A new DominoBES class has been added as a way of allowing Notes applications to communicate with a Blackberry device via the Blackberry Enterprise Server (BES). Unlike the rest of the framework, this class has been developed in Java due to the need to send an HTTP POST request. The first method - brow serChannelPush (see below) provides a wrapper for executing a Channel Push, effectively adding an icon to the Blackberry device, which when selected invokes the nominated URL. import java.net.*; public class DominoBES { public void DominoBES() { } public void browserChannelPush(String mdsHost, int mdsPort,String email,String docURL, String unreadIconURL, String readIconURL, String pushID, String pushTitle) { try { URL mdsURL = new URL("http",mdsHost,mdsPort,"push?DESTINATION=" + email + "&PORT=7874&REQUESTURI=/"); HttpURLConnection connection = (HttpURLConnection)mdsURL.openConnection(); connection.setRequestMethod("Post"); connection.setRequestProperty("X-RIM-Push-Type","Browser-Channel"); connection.setRequestProperty("X-RIM-Push-Title",pushTitle); connection.setRequestProperty("X-RIM-Push-Channel-ID",pushID); connection.setRequestProperty("X-Rim-Push-Read-Icon-URL",readIconURL); connection.setRequestProperty("X-Rim-Push-Unread-Icon-URL",unreadIconURL); connection.setRequestProperty("Content-Location",docURL); } catch (Exception e) { System.err.println("Push failure"); e.printStackTrace(System.err); } } }
import java.net.*; public class DominoBES { public void DominoBES() { } public void browserChannelPush(String mdsHost, int mdsPort,String email,String docURL, String unreadIconURL, String readIconURL, String pushID, String pushTitle) { try { URL mdsURL = new URL("http",mdsHost,mdsPort,"push?DESTINATION=" + email + "&PORT=7874&REQUESTURI=/"); HttpURLConnection connection = (HttpURLConnection)mdsURL.openConnection(); connection.setRequestMethod("Post"); connection.setRequestProperty("X-RIM-Push-Type","Browser-Channel"); connection.setRequestProperty("X-RIM-Push-Title",pushTitle)
|