Create breadcrumbs (Notes)

The following code lets you store breadcrumbs on a document, based upon the parent-child relation.

Function GetDocumentCategory(doc As notesdocument) As String
Dim session As New notessession
Dim db As notesdatabase
Dim node As notesdocument
Dim Category As String
Set node = doc

Set db = session.currentdatabase

Do While node.isResponse
If node.isvalid Then
Set node = db.getdocumentbyunid(node.parentdocumentunid)
If Category = “” Then
Category = node.Title(0)
Else
Category = node.Title(0) & “\” & Category
End If
End If
Loop

GetDocumentCategory = Category
End Function

Call the function somewhere, for example the querysave event on a form:

Sub Querysave(Source As Notesuidocument, Continue As Variant)
Dim doc As notesdocument
Set doc = source.document
Call doc.replaceitemvalue(“Path”, GetDocumentCategory(doc))
End Sub

Bildr – A photo sharing project

Bildr is a prototype application that never received enough funding to become a full blown (internal) Notes template. The idea is to offer a place where teams can share high resolution images.

Bild is  the Swedish word for photo and Bildr refers ofcourse to Flickr.

Another reason why I stopped prototyping the database is that I started looking at XPages and the limitations I experience with pre-XPages development. To shorten it: it takes just too many design elements to build it in the old-fashioned way to be able to offer a Web 2.5 experience. Maybe after a long break I will move the idea behind the application into an XPages based solution.

For now you can download the application and use it as a start for your development project.

What’s in it?

1. Easy import and thumbnailing of images (Notes client)

Open any of the Picture views and select the New Upload button.

bildr01

The idea is that you can upload multiple images at once in a new or an existing category. You can add tags and a description to the to be created document(s). New uploads you may want to appear in the ImageFlow gallery in the startpage and you can enable commenting and read restrictions.

bildr02

When you select the Upload Images action button you can select the pictures you want to upload. The code behind this function is taken from the NSFTools site (I guess). After uploading you can select to include more images or just leave it as it is.

Behind the scenes each picture is included in a Notes document with also a mid-size image and a thumbnail:

bildr03

For the Notes client that is just it. Nothing more exciting here except some gaps in the completeness of the functionality (hey, I was talking about a prototype).

2. Presentation (Browser)

For the way of presenting different elements I guess I was so bold to use Jake Howlet’s DomBlog Template (for search, calendar, commenting, RSS etc.) which was for a long time a learning application for many Domino developer. I guess if you compare the layout with that of LinkedIn you will see some comparison.

bildr05

3. Tagcloud (Browser)

For the Tagcloud I got some help.

bildr04

4. ImageFlow Gallery

bildr06

5. Lightbox

I guess Lightbox alike functions are almost unthinkable when looking at high resolution images so this I also included when clicking on a mid-size or thumbnail image:

bildr07

What is it not?

You can not call this app a production ready application. Some things are not in place (like cleaning up categories and corresponding image documents). However it can be a nice starting point for a mature application. Untill now I experienced little or none errors so please give it a try and send me the end result of you modification.

Steal with pride and improve! (11 MB)

jQuery TreeView Menu from Notes View

I was looking for a way to get a web publishing toolkit in Notes away from displaying documents in a frameset.

The frameset consists of several frames:

  • a header section
  • a leftside navigation section
  • a main or content section

The reason why a frameset is used because the leftside navigation is populated from documents in a Notes View.  This navigator would be collapsed again every time it would be re-loaded again.  It is also build by an agent that generates XML which needs to be transformed in the browser.

Here is how it looks today:

nav_current

Some searching on the web brought me to these documents:

jQuery TreeView Menu and a Show ‘n Tell Thursday blogpost. Combining the 2 topics would solve my problem since the jQuery TreeView allows to re-open the treemenu by the location specified for a menu item!

( I will add the SnTT tag to this document to honour the contributors, even when it is now still Thursday)

I soon discovered that the code for the Tree Convertor from Datatribe works with documents in the same level, meaning that documents with a response hierarchy will not be supported because you do not use categorized columns in your View. Nevertheless a little trial and error and rewriting the original code resulted in the following Notes View presented as a tree:

nav_new

Note: in the example I have already applied some styling.

In this View each ‘Category’ is a document containing one or more response documents. The highlighted (red) item is the currently opened document.

So how do you get there?

  • Create a Notes View that displays your documents as you want them to be presented in the tree. In the Datatribe example this View is called (LUDocs).

nav_notesview

  • Upload all the fields you need for the jQuery TreeView (CSS, Images, JS Libraries) and reference to them in the Head section of your Document Form:

nav_header01

  • Add the fuction that will transform the HTML list into a Tree View structure.

nav_header02

Note:  -the persist: “location”- parameter ensures that the tree will be expanded by the item / document opened by default.

  • On the place where you want to display the TreeView add the following code:

nav_new_code

As you can see a script is here called which uses an Agent to collect the data from a Notes View in an HTML format ( ul and li list format). I had to make come changes to the original code, since I am using a View that support the display of response documents in a hierarchy.

Sub Initialize
‘—This agent is designed to ouput the required javascript to produce a nested HTML list
On Error Goto errorhandler

Dim s As New notessession
Dim db  As NotesDatabase
Dim doc As NotesDocument

‘–Category passed in via URL
Dim inCategory As String

‘—View name passed in via URL
Dim viewName As String

‘—Display Category ?
Dim displaycategory As String

‘—Document in the view
Dim tmpdoc As notesdocument

‘–Category Level
Dim level As Integer
‘—Current Category level
Dim currentLevel As Integer

Dim view As NotesView
Dim i As Integer
Dim JavascriptOutput As String

‘—Due text size limits we need to hold the ouput on 1 or more temporay notes items (never saved)
Dim fieldCounter As Integer
Dim holdingField As Notesitem

Set doc=s.documentcontext

‘–Set up holding field
fieldCounter=1
Set holdingField=New notesitem(doc,”HoldingField_” & fieldCounter,””)

inCategory=getqueryparam(doc.query_string(0),”category”,””)
viewName=getqueryparam(doc.query_string(0),”viewname”,””)
displaycategory=getqueryparam(doc.query_string(0),”displaycategory”,””)

Set db=s.currentdatabase

level=0
JavascriptOutput=””

If displaycategory=”false” Then

Else
If inCategory=”” Then
JavascriptOutput=JavascriptOutput & |<h3>All Documents</h3>|
Else
JavascriptOutput=JavascriptOutput & |<h3>| & inCategory & |</h3>|
End If
End If

JavascriptOutput=JavascriptOutput & |<ul id=”tree”>|

‘—-View could be passed in via QueryString to allow a more generic agent
Set view=db.GetView(viewName)

‘—If view is not found handle it – as the view name is passed in this could be missing
If view Is Nothing Then
Print |Content-Type:text/plain|
Print |document.write(‘Error – View not found -| & viewName & |’)|
Goto getout
End If

Dim entry As NotesViewEntry
Dim nav As NotesViewNavigator
If inCategory=”” Then
Set nav = view.CreateViewNav
Else
Set nav = view.CreateViewNav
End If
Set entry=nav.GetFirst

Dim edoc As NotesDocument
Dim childdoc As NotesDocument
Dim coll As notesdocumentcollection

While Not(entry Is Nothing)

Set edoc = entry.Document
Set coll = edoc.responses

If coll.Count > 0 Then
currentLevel=entry.ColumnIndentLevel+1
Select Case (entry.ColumnIndentLevel+1)
Case level
%REM
JavascriptOutput=JavascriptOutput & |</ul></li>|
%END REM
Case Is<level
For i=2 To ((level)-entry.ColumnIndentLevel)
JavascriptOutput=JavascriptOutput & |</ul></li>|
Next
End Select

‘–Update level
level=entry.ColumnIndentLevel+1

‘–Propercase it
JavascriptOutput=JavascriptOutput & |<li><strong><a href=”http://| & returnCommonServer() & |/| & swapchars(db.FilePath,”\”,”/”) & |/0/| & entry.UniversalID & |”>| & edoc.Tx_Document_Title(0) &|</a></strong><ul>|
Else
‘—At document
currentLevel=entry.ColumnIndentLevel+1
Select Case (entry.ColumnIndentLevel+1)
Case level
%REM
JavascriptOutput=JavascriptOutput & |</ul></li>|
%END REM
Case Is<level
For i=2 To ((level)-entry.ColumnIndentLevel)
JavascriptOutput=JavascriptOutput & |</ul></li>|
Next
End Select

‘–Update level
level=entry.ColumnIndentLevel+1

Set tmpdoc=db.GetDocumentByUNID(entry.UniversalID)
Set tmpdoc=entry.Document
If tmpdoc.Tx_Document_Title(0)<>”” Then
JavascriptOutput=JavascriptOutput & |<li><a href=”http://| & returnCommonServer() & |/| & swapchars(db.FilePath,”\”,”/”) & |/0/| & entry.UniversalID & |”>| & fn_escape(Cstr(tmpdoc.Tx_Document_Title(0))) & |</a></li>|
End If
End If

‘–Check for field size
Call holdingfield.appendtotextlist(JavascriptOutput)
Call monitorFieldSize(doc ,fieldCounter,holdingField)
JavascriptOutput=””
Set entry=nav.GetNext(entry)
Wend

‘–Now we have finished we need to close the remaining tags
For i=1 To (currentLevel-1)
JavascriptOutput=JavascriptOutput & |</ul></li>|
Next

Call holdingfield.appendtotextlist(JavascriptOutput)

‘—-Now output Javascript

Print |Content-Type:text/plain|
Print |document.write(‘\n’+|
If fieldcounter=1 Then
Forall items In doc.GetItemValue(“HoldingField_1”)
Print|’| &  items & |\n’+|
End Forall
Else
For i=2 To fieldcounter
Forall items In doc.GetItemValue(“HoldingField_” & i)
Print|’| & items &  |\n’+|
End Forall
Next
Forall items In doc.GetItemValue(“HoldingField_1”)
Print|’| & items  |\n’+|
End Forall
End If
Print |’\n’)|

getout:
Exit Sub

errorhandler:

‘—As this is an agent that expects to return JavaScript we will return the error message in Javascript
Print |Content-Type:text/plain|
Print |document.write(‘Error – | & Error & | – | & Err & |’)|

Resume getout

End Sub

That is about it! Here is a better look of the result:

nav_result

Pagination on a View in the Notes client

A problem with Notes Views is that when they contain a lot of documents scrolling becomes inevitable. Even when you apply categorized columns it will become necessary that users click through the Views. An option would be that you just start typing and hopefully Notes will lead you to the nearest corresponding document.

When you apply categorized views the categories are displayed vertically below each other so for travelling users with smaller screens it will become tasly to open and close the categories just to see thse categories and corresponding documents displayed in a properly in the overview.

Maybe XPages in the Notes client will bring us some relief, but we are not there yet.

So how do we cope with the issue for the time being?

On the web it is common practice to have some sort of pagination on top / below lists of documents so you can navigate through the list without the need to scroll up and down or move you mouse across the screen. The pagination can be based on number of available pages or just available short cuts (example: the first letter of an available lastname). I myself have written some articles about pagination on this blog which you may check out yourself.

So this is all about the web, I adress this article to pagination in the Notes client.

The simplest way I found is to create a list of similar shortcuts, containing javascript, and render pass thru HTML in Notes. In this example I explain how I added pagination to a list (a Notes View) of persons. The thought is to show a list of persons form which their lastname start with a certain letter. What I needed to accomplish this:

  • A Form with an Embedded View. This View should be categorized by the first letter of the lastname @LowerCase(@Trim(Tx_LastName);1). SaveOptions are set to zero ofcourse.
  • The Embedded View has the ‘Show single category option’ in use.  As formula write the name of the Field that will acts as a temporary container (Tmp_LastName).
  • Add a Field named TmpLastName on top of the Embedded View. Make it editable. As default value give it the value “a” (you could check if this value is really available or not).
  • On top of the Form add the following JavaScript and CSS code:

<script language=”Javascript”>
var entryNumber = “”;
function showTab(lastName) {
entryNumber = lastName;
document.forms[0].LastName.value = entryNumber;
entryNumber = “”;
document.forms[0].set_field.click()
}
</script>

<style type=”text/css”>
font {
font-family: “Default Sans Serif”, sans-serif;
font-style: normal;
font-weight: normal;
font-size: 8pt;
color: black;
text-decoration: none;
text-align: left;
text-indent: 1ex;
}
A:link {color: blue; text-decoration: none}
A:visited {color: blue; text-decoration: none}
A:active {color: blue; text-decoration: none}
A:hover {color: blue; text-decoration: none}
</style>

  • As final step you need to add a computed text just above the Embedded View. Mark the text as passthru HTML. A Value enter:

varList:=@DbColumn( “”: “NoCache” ; “” : “” ; “PersViewCategorized” ; 1 );
tmpList:=@Implode(“<a href=\”javascript:showTab(‘” + varList + “‘)\”>” + varList+ “</a>” + ” | ” );
@LeftBack(tmpList;”|”)

Here we make a call to first column in the same Embedded View. Around each found category we wrap a JavaScript call which will put the value in the temporary field and finally we call the button to update the UI.

Here is a screendump that shows how it could look like:

paginationNotes

A problem discovered

Well everything  looked shiny untill I tried to load the Form in a Frameset. When pressing on one of the links I got the message:

paginationNotesMessage

Some work to do IBM….

Mimicing the preview-switch option

I thought I give this Thursday a ‘SnTT’ swing (what happened with that phenomena?).

My collegue Tomas can be sometime tough with progressive ideas, but this makes you just try that extra effort to make something in Notes a little bit better than expected.

When I asked him for ideas for a standard development template he liked the ideas to have multiple collapsible preview panes BUT he wanted the option for the user to switch in preview in bottom or preview on side, just like in the Notes 8 mail template.

previewbutton

Since we know that the mail is a composite application with it specific functionality a different approach to provide something similar in the ‘classic’ client.

Solution

The following image shows the frameset for my application:

main_frameset2

So in frame ‘main’ I have a frameset. By default the frameset is the one that contains a preview on the bottom. This frameset is called ‘$fs-notes-documents-vr’ (vr for vertical). I have a second frameset that is called ‘$fs-notes-documents-hr’ (hr vor horizontal) that will be displayed in frame ‘main’ when the user chooses for a preview on side.

In my horizontal menu (alternative menu) I have added 2 action buttons:

  • Preview on Bottom.

@SetTargetFrame(“Main”);
@Command( [OpenFrameset] ; “$fs-notes-documents-vr” );
@Environment(“AppCode-AppPreview”;”$fs-notes-documents-vr”)

  • Preview on Side.

@SetTargetFrame(“Main”);
@Command( [OpenFrameset] ; “$fs-notes-documents-hr” );
@Environment(“AppCode-AppPreview”;”$fs-notes-documents-hr”)

These actions will place the appropiate frameset in frame ‘main’ and set an environment variable. This variable is being read when the ‘default’ frameset (the frameset that contains frame ‘main’) is being used when opening the application.

For frame ‘main’ I have as computed value:

varFrSetDefault:=@Environment( “AppCode-AppPreview” );
@If ( varFrmSetDefault = “” ; “$fs-notes-documents-vr” ; varFrmSetDefault)

So if the user has used the application before and has set a preview preference the next time the user comes in the application the ‘prefered’ frameset / preview option is being used!

Too bad there is no option to read which design element is currently used in a frame (like in a @GetTargetFrame). In that way I would have been able to re-open the currently opened view / folder (okej I could use another environment variable for that).

Maintaining the universal identifier when copying documents from one App. to another

I received an incident that some documents where removed from an application in production. Since it was not sure when the documents where deleted and what other changes have occured in the application the quickest way was to quickly copy the documents from the recovery database back into the production database.

Unfortunately the documents are being linked on our intranet so a copy and paste action would modify their universal ids.

This code will reset the universal ID to the one of the original even though a copy is being made via an agent:

Sub Initialize

Dim ws As New NotesUIWorkspace

Dim destinationDb As New NotesDatabase( “ServerName”, “directory\application.nsf” )

Dim uiView As NotesUIView

Set uiView = ws.Currentview

Dim dc As NotesDocumentCollection

Set dc = uiView.Documents

Dim orgDoc  As NotesDocument

Dim newDoc  As NotesDocument

Dim junkUNID As String

Dim dontWantThisDoc  As NotesDocument

Set orgDoc = dc.GetFirstDocument

Do Until (orgDoc Is Nothing)

Set newDoc = orgDoc.CopyToDatabase(destinationDb)

junkUNID = newDoc.UniversalID

newDoc.UniversalID = orgDoc.UniversalID

Set dontWantThisDoc = destinationDb.GetDocumentByUNID(junkUNID)

‘Call dontWantThisDoc.Remove(True)

Set orgDoc = dc.GetNextDocument(orgDoc)

Loop

End Sub

Top 10 custom JavaScript/LotusScript functions of all time

Just a quickie:

Top 10 lists are always nice (and handy) so I read with great interest Dustin Diaz’s posting on his Top 10 of custom JavaScript functions of all time.

After reading I googled after the Top 10 custom LotusScript functions of all time. I guess there is room for such a posting (hint).

Since I mostly do more Web-development than Notes-development the number of custom LS functions for me is limited, but nevertheless I use them in agents.

So here you find 10 custom functions than I (have) use(d) on a regular base (not ranked in importance). Please feel free to add your supertrooper LS functions:

===1===

Function DBColumn (strClass As String, strNoCache As String, strServer As String, strDatabase As String, strView As String, strKey As String) As Variant
 Dim strFormula As String,quotes As String
 quotes = Chr(34)
 strFormula = “@DbColumn(” & quotes & strClass & quotes & “:” & quotes & strNoCache & quotes & “;” & quotes & strServer & quotes & “:” & quotes & strDatabase & quotes & “;” & quotes & strView & quotes & “;” & strKey & “)”
 DbColumn = Evaluate( strFormula )
End Function

===2===

Function DBLookup (strClass As String, strNoCache As String, strServer As String, strDatabase As String, strView As String, strKey As String, strReturn As String) As Variant
 Dim strFormula As String,quotes As String
 quotes = Chr(34)
 strFormula = “@DbLookup(” & quotes & strClass & quotes & “:” & quotes & strNoCache & quotes & “;” & quotes & strServer & quotes & “:” & quotes & strDatabase & quotes & “;” & quotes & strView & quotes & “;” & quotes & strKey & quotes & “;” & strReturn & “)”
 DbLookup = Evaluate( strFormula )
End Function

===3===

Function LeftStr(OrigStr, LeftOf ) As String
 Dim Pos As Integer
 Dim OrigStrLen As Integer
 Pos = Instr( Lcase(OrigStr), Lcase(LeftOf) )
 OrigStrLen = Len(OrigStr)
 If pos>0 Then
  LeftStr = Left( OrigStr, (Pos-1))
 Else
  LeftStr = OrigStr
 End If
End Function

===4===

Function RightStr(OrigStr, RightOf ) As String
 Dim Pos As Integer
 Dim OrigStrLen As Integer
 Dim RightOfLen As Integer
 Pos = Instr( Lcase(OrigStr), Lcase(RightOf) )
 OrigStrLen = Len(OrigStr)
 RightOfLen = Len(RightOf)
 If Pos>0 Then
  RightStr = Right( OrigStr, OrigStrLen -(RightOfLen+Pos-1))
 Else
  RightStr = OrigStr
 End If
End Function

===5===

Function ReplaceSubString(SourceS As String, SearchS As String, ReplaceS As String) As String
 While Instr(SourceS, SearchS) > 0
  SourceS = Left$(SourceS, Instr(SourceS, SearchS) – 1) + ReplaceS + Right$(SourceS, Len(SourceS) – Instr(SourceS, SearchS) – Len(SearchS) + 1)
 Wend
 ReplaceSubstring = SourceS
End Function

===6===

Function Unique(vIn As Variant) As Variant
 Dim lsTemp List As String     ‘Create the list
 Dim astemp() As String        ‘A place to store the compacted array
 Dim iCount As Integer         ‘Count how many uniques we find
 
  ‘Make sure they sent us an array of strings
 If Not Isarray(vIn) Then
  Msgbox “Unique requires an array as input”
  UniqueA = vIn
  Exit Function
 Elseif Typename( vIn(0) ) <> “STRING” Then
  Msgbox “Unique requires an array of strings as input. vIn(0) is a ” _
  + Typename( vIn(0) )
  UniqueA = vIn
  Exit Function
 End If
 
 Forall s In vIn
     ‘If the entry isn’t in the list…
  If Not Iselement( lsTemp(s) ) Then
       ‘Add it to the list
   lsTemp(s) = “”
   iCount = iCount + 1
  End If
 End Forall
 
 ‘Note that there’s no “preserve” keyword here so this is relatively quick
 Redim asTemp(iCount-1)
 
 iCount = 0
  ‘Copy all the unique elements into the temp array
 Forall v In lsTemp
  asTemp(iCount) = Listtag(v)
  iCount = iCount + 1
 End Forall
 
  ‘Return the temp array
 UniqueA = asTemp
End Function

===7===

Function SortCollection(coll As NotesDocumentCollection, fieldnames() As String) As NotesDocumentCollection
 
‘ ————————————————
‘ — You may use and/or change this code freely
‘ — provided you keep this message
‘ —
‘ — Description:
‘ — Sorts and returns a NotesDocumentCollection
‘ — Fieldnames parameter is an array of strings
‘ — with the field names to be sorted on
‘ —
‘ — By Max Flodén 2005 – http://www.tjitjing.com
‘ ————————————————
 
 Dim session As New NotesSession
 Dim db As NotesDatabase
 Dim collSorted As NotesDocumentCollection
 Dim doc As NotesDocument
 Dim i As Integer, n As Integer
 Dim arrFieldValueLength() As Long
 Dim arrSort, strSort As String
 Dim viewname As String, fakesearchstring As String
 
 viewname = “$All” ‘This could be any existing view in database with first column sorted
 fakesearchstring = “zzzzzzz” ‘This search string must NOT match anything in view
 Set db = session.CurrentDatabase
 
‘ —
‘ — 1) Build array to be sorted
‘ —
 
‘Fill array with fieldvalues and docid and get max field length
 Redim arrSort(0 To coll.Count -1, 0 To Ubound(fieldnames) + 1)
 Redim arrFieldValueLength(0 To Ubound(fieldnames) + 1)
 For i = 0 To coll.Count – 1
  Set doc = coll.GetNthDocument(i + 1)
  For n = 0 To Ubound(fieldnames) + 1
   
   If n = Ubound(fieldnames) + 1 Then
    arrSort(i,n) = doc.UniversalID
    arrFieldValueLength(n) = 32
   Else
    arrSort(i,n) = “” & doc.GetItemValue(fieldnames(n))(0)
‘ Check length of field value
    If Len(arrSort(i,n)) > arrFieldValueLength(n) Then
     arrFieldValueLength(n) = Len(arrSort(i,n))
    End If
   End If
   
  Next n
 Next i
 
‘Merge fields into list that can be used for sorting using @Sort function
 For i = 0 To coll.Count – 1
  If Not strSort = “” Then strSort = strSort & “:”
  strSort = strSort & “”””
  For n = Lbound(fieldnames) To Ubound(fieldnames) + 1
   strSort = strSort & Left(arrSort(i,n) & Space(arrFieldValueLength(n)), arrFieldValueLength(n))
  Next n
  strSort = strSort & “”””
 Next i
 
‘ —
‘ — 2) Sort array
‘ —
 arrSort = Evaluate(“@Sort(” & strSort & “)”)
 
‘ —
‘ — 3) Use sorted array to sort collection
‘ —
 Set collSorted = coll.Parent.GetView(viewname).GetAllDocumentsByKey(fakesearchstring)
 
 For i = 0 To Ubound(arrSort)
  Set doc = db.GetDocumentByUNID(Right(arrSort(i), 32))
  Call collSorted.AddDocument(doc)
 Next i
 
‘ —
‘ — 4) Return collection
‘ —
 Set SortCollection = collSorted
 
End Function

===8===

Function AddToList (Value As Variant, ValueList As Variant)
 Dim tmpValueList As Variant
 ‘ Load the array element by element so that the datatypeis preserved
 Redim tmpValueList(Ubound(ValueList))
 For i = 0 To Ubound(ValueList)
  tmpValueList(i) = ValueList(i)
 Next
 ‘ Determine if we are dealing with a new list, if absolutely no values in the first entry, then add new value to 0
 If Ubound(tmpValueList) = 0 And Cstr(tmpValueList(0)) = “” Then
  x = 0
 Else
  x = Ubound(tmpValueList) + 1
 End If
 Redim Preserve tmpValueList(x)
 tmpValueList(x) = Value
 AddToList = tmpValueList
End Function

===9===

Function createTable(FldTitles As Variant ,FldNames As Variant, doccoll  As notesdocumentcollection ,rtitem As NotesRichTextItem,msgTitle As String,msgBody As String ) As NotesRichTextItem
 ‘Takes Documentcollection & creates tabular information on to the passed  rtitem (rich text item)
 
 Dim TempNitem As NotesItem
 Dim TempNm As NotesName
 
 Set ritem=rtitem
 Set rtnav = ritem.CreateNavigator
 Set rstyle=session.CreateRichTextStyle
 
 ‘===================================================
 ‘heading in the body section of the mail
 rstyle.Bold=True
 rstyle.NotesColor=COLOR_RED
 rstyle.Underline=True
 rstyle.NotesFont=FONT_COURIER
 rstyle.FontSize=12
 Call  ritem.AppendStyle(rstyle)
 ritem.AppendText(msgTitle) 
 rstyle.Underline=False
 rstyle.NotesColor=COLOR_BLACK
 ritem.AddNewline(2) 
 rstyle.FontSize=10
 rstyle.Bold=False
 rstyle.NotesColor=COLOR_BLACK
 Call  ritem.AppendStyle(rstyle)
 ritem.AppendText(msgBody)
 ritem.AddNewline(1)
 
 ‘===================================================
 rows=doccoll.Count +1
 cols=Cint(Ubound(FldTitles)+1)
 Call ritem.AppendTable(rows,cols)
 Call rtnav.FindFirstElement(RTELEM_TYPE_TABLECELL)
 ‘=================================================
 ‘heading of the table
 rstyle.Bold=True
 rstyle.NotesColor=COLOR_BLUE
 rstyle.FontSize=10
 Call  ritem.AppendStyle(rstyle)
 For i=0 To Ubound(FldTitles)
  Call ritem.BeginInsert(rtnav)
  Call ritem.AppendText(FldTitles(i))
  Call ritem.EndInsert
  Call rtnav.FindNextElement(RTELEM_TYPE_TABLECELL)
 Next
 ‘=================================================
 rstyle.FontSize=10
 rstyle.Bold=False
 rstyle.NotesColor=COLOR_BLACK
 Call  ritem.AppendStyle(rstyle)
 Set  doc=doccoll.GetFirstDocument
 While Not (doc Is Nothing)
  For i=0 To Ubound(FldNames)
  ‘check for date/ names document link
   Call ritem.BeginInsert(rtnav)
   If FldNames(i)=”Doc_Link” Then
    Call ritem.AppendDocLink(doc,doc.Created)
   Else
    Set TempNitem=doc.GetFirstItem(FldNames(i))   
    If TempNitem.IsNames Then
     Set TempNm=Nothing
     Set TempNm=New NotesName(TempNitem.Values(0))
     Call ritem.AppendText(TempNm.Common)
    Elseif Isdate(TempNitem.Values(0)) Then
     Call ritem.AppendText(Format(TempNitem.Values(0),”DD-MMM-YYYY”))  
    Else
     Call ritem.AppendText(TempNitem.Values(0))    
    End If
   End If
   Call ritem.EndInsert
   Call rtnav.FindNextElement(RTELEM_TYPE_TABLECELL)  
  Next
  Set doc=doccoll.GetNextDocument(doc)
 Wend
 Set CreateTable=ritem
End Function

===10===

Function GetProfile(ProfName As String) As notesdocument
     ‘Get Database Profile and return to variable
 On Error Resume Next
 Print “Opening Database Profile”
 Set getProfile=Db.GetProfileDocument( profName)
 If Not getProfile Is Nothing Then
  var=getProfile.IsProfile
  If Not var Then Set getProfile=Nothing
 End If
End Function

WebQueryOpen and Computed fields

Well finally I am getting a bit more back on the development track. So I might spend some times here with dropping some code.

One of my latest ‘how do I do that?’ challenges was to write values collected by a WQO agent into a Form. 

So how do you do this with computed fields?

Well you get the documentcontext just like you would normally do:

Dim s As New notessession
Dim db As notesdatabase
Set db = s.currentdatabase 
Dim doc As notesdocument
Set doc = s.documentcontext

An editable field you would set in this way:

doc.fieldX = “collected data”

But as expected this will come up as editable value in the field name in the web form. You could att the htmlattribute “type = hidden” but I could navigate to that document via the DOM structure of the page.

If you would avoid that you could try writing to a computed field using these statements:

doc.cfieldX=”collected data”
doc.cfieldXWQO=”collected data””

  • cfieldX is the editable field, set type=”hidden”
  • cfieldXWQO is the computed field, set as computed value ‘cfieldXWQO’.

Note:

If you would save the document via a normal way (@command([FileSave]) or a JS submit() the value in the field cfieldXWQO is not saved. So in order to save the collected data you need to use a WebQuerySave agent…

Validate email address (LotusScript)

Yesterday I spent some time on googling looking for a decent LS function that validates an email internet address. 

The evaluate method on

@ValidateInternetAddress( [ addressFormat ] ; Address )

validates only on characters before and after the @ sign, so it does not check the top level domain (.com, .org or .se) and was therefor of no use.

Well, what did Google bring me then? Nothing of interest unfortunately.

Tomas sent me the function below but when testing it it does not properly checked if a username is given (‘@acme.com’ would validate).

A simple combination of these two checks will do the job but this looks a bit poor. Maybe you have a better solution?

Function CheckSMTPAddressChar( smtpAddress As String) As Boolean
%REM – Check to see if any incorrect characters is used in the SMTP address
smtpAddress  : the SMTP address to validate
return    : will return true if valid email address
%END REM
 Dim notAllowed As Variant
 Dim noNotAllowed As Integer
 Dim char As String
 Dim charNum As Integer
 Dim noAtChar As Integer
 Dim i As Integer
 
 CheckSMTPAddressChar = True
 
 For i = 1 To Len( smtpAddress)
  char = Mid( smtpAddress, i, 1)
  If char = ” ” Then char = “<space>”
  charNum = Asc( char)
  REM  No local characters is allowed only … normal once
  Select Case charNum
  Case 65 To 90  ‘ 65 = a, 90 = z
  Case 97 To 122  ‘ 97 = A, 122 = Z
  Case 48 To 58  ‘ 48 = 0, 58 = 9
  Case 45    ‘ 45 = –
  Case 46    ‘ 46 = .
  Case 64    ‘ 64 = @
  REM  We can have 1 char 46 = @ but not 2
   noAtChar = noAtChar + 1
   If noAtChar > 1 Then
    CheckSMTPAddressChar = False
   End If
  Case 95    ‘ 57 = _
  Case Else
   CheckSMTPAddressChar = False
  End Select
 Next
End Function

===//===

Dim smtpAddress As String
 smtpAddress = “patrick.kwinten@acme.com
 If CheckSMTPAddressChar(smtpAddress) And smtpAddress Like “*@*.*” Then
  ‘do a double check
  Dim emailChecker As Variant
  Dim checkThis()
  Redim checkThis(0)
  checkThis(0) = smtpAddress
  emailChecker = Evaluate(|@ValidateInternetAddress([Address821];”| + checkThis(0) + |”)|)
  If emailChecker(0) = “” Then
   Print “Valid Email address!”
  Else
   Print “Email address not valid!”
  End If    
 Else
  Print “Email address not valid!”
 End If
End Sub

Combine column lookups

Hejsan. I will pick the quickest way to post something in the SNTT category by just adding an image which explains itself. It is probably taken from a LotuSphere session (guess the session?).

I recently used in in updating an application that was dated somewhere from 1998 and used @DBColumns in a form more than 30 times, so adding this technique was a major performance improvement…

combining multiple dbcolumns
combining multiple dbcolumns