Perfect pagination style using CSS
One of the blogs I often read is the one from Antonio Lupetti who is a Web developer, not a Notes developer. Especially his article about pagination caught my attention since it remembered me about the idea to rewrite Bob Obringers ‘ultimate view navigator’.
Bob uses in his approach for the pagination a table to display the page numbers where a list should be more suitable.
As the image below shows with Antonio’s approach it is pretty easy to make the pagination more compatible with examples seen on other well known sites. Here is a Flickr alike pagination for a Domino view:

Anything new under the sun? Not really, but for the ‘purists’ under us more esthetic solution.
Ofcourse a working example can be found here.
Mail an action through Outlook
Some people are just not that fortunate to have the privilige to be working with such an excellent tool as Lotus Notes is. In order to give them the option to add an action document created in a Notes application to their todo list in Outlook the code below will do the job:

Sub Click(Source As Button)
Dim ws As New NotesUIWorkspace
Dim session As New NotesSession
Dim db As NotesDatabase
Dim uiddoc As NotesUIDocument
Set db = session.CurrentDatabase
Set uidoc = ws.Currentdocument
Set doc = uidoc.document
If uidoc.IsNewDoc Then
Msgbox “This document has not been saved.” & Chr$(10) & Chr$(10) & “Please save prior to mailing this action!”, 4112, “New Document”
Exit Sub
End If
Const Formula$ = { @DbLookup(”"; “”; “$People”; AssignedTo; “Email”) }
Dim namesList As Variant
Set myOlApp = CreateObject(”Outlook.Application”)
Set myNameSpace = myOlApp.GetNameSpace(”MAPI”)
Set myFolder = myNameSpace.GetDefaultFolder(13)
Set myItem = myOlApp.CreateItem(3)
Dim rtItem As NotesRichTextItem
Set rtItem = doc.GetFirstItem(”Comment1″)
With myItem
.Assign
namesList = Evaluate(Formula$, doc)
Forall names In namesList
Set myRecipients = .Recipients.Add(names)
End Forall
.Subject = doc.Subject(0)
.Body = “Action number: ” & doc.ActionNumber(0) & Chr$(10) & Chr$(10) & rtItem.text
If (doc.NoDueDate(0)=”") Then
.DueDate = doc.DueDate(0)
End If
.Importance = doc.Priority(0)
.Status = doc.Status(0)
.Categories = doc.Category(0)
.Display
End With
doc.OutlookSave = Date$
Set myOlApp = Nothing
Set myFolder = Nothing
Set myItem = Nothing
Set myRecipients = Nothing
Exit Sub
End Sub
Get keyword function
Here a simple LotusScript function that I use quite a lot to get keyword values:
Function Get_Keyword(dbCurrent As NotesDatabase, sKey As String) As NotesDocument
Dim viewKeywords As NotesView
Dim docReturn As NotesDocument
Set viewKeywords = dbCurrent.GetView(”v-keywords”)
Set docReturn = viewKeywords.GetDocumentByKey(sKey)
Set Get_Keyword = docReturn
End Function
Usage:
Set keyword = Get_Keyword(dbCurrent, “keyword_key”)
where keyword_key is the first fieldvalue in the view to search for…
Demo ‘collapse embedded view by default’
Today it was here very rainy so I hate plenty of time to do those things you wtill wanted/needed/were asked to do. For example I receive frequently question to send a working example for collapsing an embedded view by default. I have explained the principles in a previous writing.
Therefor I keep this blog entry short with just remaining a reference to the location where you can download a working-example.
Export to Excel wizard
I was asked to take a look at an existing application, and come up with suggestions to improve performance.
In a first look I found that most of the views, especially the default to be opened view, were set to refresh the index automatically… you can imagine how much the users love to start working with this application which has readers fields on all documents (NOT!).
When I asked were all the views were needed for, I got as reply: to have it in a format we want to make graphics in Excel.
Okej, why not delete these ‘views to be exported for Excel’ and replace them with an export functionality?

So what where the needs?
- a set of fields for different types of forms that should be manageble by the application manager
- an export-mechanism to Excel which should be easy to understand for end-users and give them the opportunity to create customizable exports.
1) Export profiles
In order to create so-called Export profiles for different Notes forms I needed an additional form (ExcelFormExport) where the administrator can:
- select the desired form for export (FieldA)
- select the form-fields that should be available for export (FieldB)
This last field is being calculated when FieldA is being exited:
Sub Exiting(Source As Field)
Dim workspace As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set uidoc = workspace.CurrentDocument
fieldvalue = Lcase(uidoc.FieldGetText(”Tx_FormSelected”))
Dim doc As NotesDocument
Set doc = uidoc.document
If fieldvalue <> “” Then
Dim session As New NotesSession
Dim db As NotesDatabase
Set db = session.CurrentDatabase
Forall form In db.Forms
If Lcase(form.Name) = fieldvalue Then
If Isempty(form.Fields) Then
Messagebox form.Name & ” has no fields…”
Else
’collecting the form fields
Dim arrFieldNames() As String
Dim iCount As Integer
iCount = 0
Redim arrFieldNames(0)
Forall field In form.Fields
Redim Preserve arrFieldNames(iCount)
arrFieldNames(iCount) = field
iCount = iCount + 1
End Forall
Call doc.ReplaceItemValue(”Tx_FormFields”, BubbleSort(arrFieldNames))
Call uidoc.Refresh()
End If
Exit Sub
End If
End Forall
Messagebox “The form “”" & formNameIn & “”" does not exist”
End If
End Sub
Bubblesort is a function which sorts the array with fieldnames:
Function BubbleSort(vtList As Variant) As Variant
Dim tmpValue As Variant
Dim x As Integer, y As Integer
If Ubound(vtList) = 1 Then
BubbleSort = vtList
Exit Function
End If
For x = 0 To Ubound(vtList)
For y = (x + 1) To Ubound(vtList)
On Error Resume Next
If vtList(x) > vtList(y) Then
tmpValue = vtList(x)
vtList(x) = vtList(y)
vtList(y) = tmpValue
End If
Next
Next
BubbleSort = vtList
End Function
Luckily I could make use of a solution from my colleague Tomas Ekström (his so-called ‘tablewalker’) which made the selection of fields and giving them for the end-user understandable ‘labels’ in the wizard:

The next step was creating a dialog between the available ‘Export profiles’ and the Export to Excel solution written by Ken Pespisa.

In order to display the ‘Export profiles’ in the dialoglist I had to re-write Ken’s solution a bit. I advice you to use Ken’s standard solution in stead.
Well, the rest of this export wizard is just following Ken’s work.

What I tried to achieve with this solution is:
- having an easy way for an administrator to define ‘Export profiles’ based upon information on Notes forms.
- presenting an understandable interface for the end-user that enables to create customizable exports to Excel
- get rid of a large set of nasty Notes views!
A demo you can download here.
Newsletter profile
Currently I am working on a function where users can create a profile for a newsletter. In the profile the user can specify a search query and some options.
It all begins with a simple View Action button:

The button will check if for the current user already a profile document exists, if so the document will be opened in edit mode, otherwise the form will be opened (the client requested to make all profiles public…)
varUser:=@Name([Abbreviate];@UserName);
@If(
@IsError(@DbLookup(”":”NoCache”;”";”v-userprofiles”;varUser;”Tx_DocID”));
@Do(
@Command([Compose];”F-PU”)
);
@Do(
@Command([OpenView];”v-userprofiles”;varUser);
@Command([OpenDocument];”1″)
)
)
The form itself is pretty plain and simple. The user can add more criteria to the search query via ‘AND’ or ‘OR’ options. Default we search on only one type of document.
While specifying the criteria, the user sees what he/she is adding to the query:

The code behind the ‘ADD’ button is this:
varForm:= “Document”;
varField:=Tx_Fields;
varValues:=@Implode(”\”" + Tx_Values + “\”" ;”:”);
@If(Tx_Option=”AND”;
@Do(
varConstructor:= ” & ” + varField + ” = ” + varValues;
@SetField( “Tx_Query” ; Tx_Query + varConstructor );
@SetField( “Tx_LastValue” ; varConstructor )
);
@Do(
@If(@Left(@GetField( “Tx_LastValue” );”)”)=”";
@Do(
varPrevValue:= @Right(@GetField( “Tx_LastValue” );”& “);
varConstructor:= ” & ( ” + varPrevValue + ” | ” + varField + ” = ” + varValues + ” )”
);
@Do(
varPrevValue:= @LeftBack(@GetField( “Tx_LastValue” );”)”);
varConstructor:= varPrevValue + ” | ” + varField + ” = ” + varValues + ” )”
));
varNewQuery:= @ReplaceSubstring( Tx_Query; Tx_LastValue ; varConstructor );
@SetField( “Tx_Query” ; varNewQuery );
@SetField( “Tx_LastValue” ; varConstructor )
));
@SetField( “Tx_Fields” ; “”);
@SetField( “Tx_Values” ; “”);
@SetField( “Tx_Options” ; “”)
I also added a button so the user can check if the specified query results in any usefull document collection. The code behind this button is:
Sub Click(Source As Button)
Dim workspace As New NotesUIWorkspace
Dim session As New NotesSession
Dim db As NotesDatabase
Set db = session.CurrentDatabase
Dim uidoc As NotesUIDocument
Set uidoc = workspace.CurrentDocument
searchFormula$ = uidoc.FieldGetText( “Tx_Query” )
Dim collection As NotesDocumentCollection
Set collection = db.Search(searchFormula$, Nothing, 0)
numDocs% = collection.count
If numDocs% = 0 Then
Messagebox “There are no matching documents” , , “Search Result”
Exit Sub
Else
answer% = Messagebox ( (numDocs% & ” document(s) match this query. “), MB_OKCANCEL + MB_ICONQUESTION, “Search Result”)
End If
End Sub
Because the Newsletter function has to run on a scheduled basis I added some additional options:
- How often a Newsletter should be sent
- The number of document links in the Notes Newsletter
- How old corresponding documents may be
Note: in the timestamp you can add for a search Notes uses the modified date, so this is the date the document has been modified (nice if you working on a test environment with a copy so all the documents ARE modified). You can also add this option as criteria in your search formula ofcourse.
On the bottom of the form I added a button where the user can instantly receive a Notes Newsletter with everything he/she has specified. The code for the button is:
Sub Click(Source As Button)
Dim workspace As New NotesUIWorkspace
Dim session As New NotesSession
Dim db As NotesDatabase
Set db = session.CurrentDatabase
Dim uidoc As NotesUIDocument
Set uidoc = workspace.CurrentDocument
Dim doc As NotesDocument
Set doc = uidoc.Document
searchFormula$ = uidoc.FieldGetText( “Tx_Query” )
maxListSize = uidoc.FieldGetText( “Tx_QueryMax” )
daysAdjust = doc.Tx_QueryDays(0)
Dim dateTime As New NotesDateTime( Now )
Call dateTime.AdjustDay( -daysAdjust )
Dim collection As NotesDocumentCollection
Set collection = db.Search(searchFormula$, dateTime, Cint(maxListSize))
numDocs% = collection.count
If numDocs% = 0 Then
Messagebox “There are no matching documents” , , “Search Result”
Exit Sub
Else
answer% = Messagebox ( (numDocs% & ” document(s) found. Creating a Newsletter…”), MB_OKCANCEL + MB_ICONQUESTION, “Search Result”)
Dim newsletter As NotesNewsletter
Set newsletter = New NotesNewsletter( collection )
newsletter.DoSubject = True
newsletter.SubjectItemName = “Tx_Title”
Dim newsletterdoc As NotesDocument
Set newsletterdoc = newsletter.FormatMsgWithDoclinks( db )
newsletterdoc.Form = “Memo”
newsletterdoc.Subject = “Business Intelligence Newsletter - Your results”
Call newsletterdoc.Send( False, uidoc.FieldGetText( “Tx_UserName” ))
End If
End Sub
Some parts of the code above I will re-use in the scheduled agent that sends out the Newsletters on a scheduled base.
Wrap-up: a simple handy function that might save you creating some additional views…
(User) Profile documents & mandatory fields
It has been a while since I wrote here something so I will just write something I did today. Right now I am doing some prototyping for a demo-application (web) which generally will be an ordering system.
The application uses currently (user) profile documents. It was a bit hard to find code to copy & paste, because most examples where not so detailed (just words, no code) or they used actual Notes documents, not profile documents.
So how does it work? When opening the form via an url (form_name?OpenForm) a WebQueryOpen Agent is initiated which runs the following simplified code:
Sub Initialize
Dim s As New NotesSession
Dim db As NotesDatabase
Dim profiledoc As NotesDocument
Set db = s.CurrentDatabase
Set profiledoc = db.GetProfileDocument(”UserProfile”, s.UserName)
Dim currentdoc As NotesDocument
Set currentdoc = s.documentcontext
currentdoc.Tx_UserLanguage = profiledoc.Tx_UserLanguage(0)
currentdoc.Tx_UserCostCenter = profiledoc.Tx_UserCostCenter(0)
currentdoc.Tx_UserGLAccount = profiledoc.Tx_UserGLAccount(0)
currentdoc.Tx_UserTechName = profiledoc.Tx_UserTechName(0)
currentdoc.Tx_UserTechPhone = profiledoc.Tx_UserTechPhone(0)
currentdoc.Tx_UserDeliveryStation = profiledoc.Tx_UserDeliveryStation(0)
End Sub
So basically it picks (when available) the profiledoc and copies values from it to the opened form. I could have used IsProfile to check if we actually working with a profile document…
In the WebQuerySave event I run a different code which writes away information entered in the form to the profile document:
Sub Initialize
Dim s As New NotesSession
Dim db As NotesDatabase
Dim profiledoc As NotesDocument
Set db = s.CurrentDatabase
Set profiledoc = db.GetProfileDocument(”UserProfile”, s.UserName)
Dim currentdoc As NotesDocument
Set currentdoc = s.documentcontext
profiledoc.Tx_UserLanguage = currentdoc.Tx_UserLanguage(0)
profiledoc.Tx_UserCostCenter = currentdoc.Tx_UserCostCenter(0)
profiledoc.Tx_UserGLAccount = currentdoc.Tx_UserGLAccount(0)
profiledoc.Tx_UserTechName = currentdoc.Tx_UserTechName(0)
profiledoc.Tx_UserTechPhone = currentdoc.Tx_UserTechPhone(0)
profiledoc.Tx_UserDeliveryStation = currentdoc.Tx_UserDeliveryStation(0)
Call profiledoc.Save( True, False )
End Sub
Later I think will use a similar functionality to write information back to the profile document, that hasn’t been entered before in the user profile, from any sensible point in the application (like in a final order form)…
The first ‘problem’ i faced was that a ‘normal’ JavaScript submit would give me a ‘form processed’ message. Since I had a nice ‘update’ button in mind so the user should stay in the form opened I was ‘forced’ to use a common known Domino ‘work around’.

The reference under the ‘Save Setting’ link inititates a JS function which actually initiates the function under a Notes button, that is being stored in a hidden <div>
function submitForm(){
document.all.MySaveButton.onclick();
}

So the JS function initiates the @Command([FileSave]) function which initiates the WebQuerySave event and thereby the user profile is being updated on the background.
Since the document will be re-opened due to a refresh (don’t ask me why what forces this) the WebQueryOpen events initiates again and the saved information is being re-placed on the refreshed form…
So far so good, where is the news?
I wanted to make visible which fields are supposed to be mandatory and highlight them like in the next example:

Here you have ‘normal’ fields with a gray border, and ‘normal’ entry fields that are supposed to be also ‘mandatory’. So for these fields I add both classnames.

On the JS onload event I initiate the following function:
checkISValid();
The code for this function is pretty basic and contains (in this example) no actualy validation, just a check if the field is empty or not:
function checkISValid(){
var checkFields = new Array();
checkFields = getElementsByClassName(document, “input”, “mandatoryField”)
for (x in checkFields){
if (checkFields[x].value != “” ){
checkFields[x].className = “entryField”;
}
}
}
So if the field with classname mandatoryField is empty it will get the mandatoryField class ‘removed’ (overwritten) and it will look like this:

I think in this way it is pretty clear for the user which information is still missing…
Here is the ‘getElementsByClassName’ function borrowed from this site:
function getElementsByClassName(node,tag,searchClass) {
var classElements = new Array();
if ( node == null )
node = document;
if ( tag == null )
tag = ‘*’;
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp(”(^|\\s)”+searchClass+”(\\s|$)”);
for (i = 0, j = 0; i < elsLen; i++) {
if ( pattern.test(els[i].className) ) {
classElements[j] = els[i];
j++;
}
}
return classElements;
}
Ps. for some nice JS onFocus effect like this:

You could add something like this:

Don’t forget to reset coloring on the onBlur event ![]()
Deleting documents in a view via icons
When I was reading The View article ‘Using AJAX techniques in Domino Applications, Part 2′ I noticed a functionality in the sample application I was not aware of before, but could be very usefull in my applications.
In the sample DB you can move up, downwards and delete documents in a view by icons for each document.
I only needed the delete function so that is what this item is about.

First in the view you define a column, set the property of ‘Display values as icons’, define a column value (a reference to an image resource) and in the last / programmatic use tab you enter a ‘Name’, in this case ‘Delete’:

In the Globals of that same view you make a reference to a Script Library called ‘Controls’:

The Script Library itself is not so spectaculair. In the Declarations you add:
%INCLUDE “LSConst.lss”
You also add a Sub function, in this case it is called DeleteItem. The main statement in this Sub is the line:
Call Qdoc.Remove ( True )Which is the document the user will click on in the view. In order to make the changes noticeable for the user the following statement is also added:
Set view = db.GetView(”v-projects”)
Call view.refresh
If you are interested in the function, I advice you to download the application if possible and see for yourself if it is usable for you…
Creating a ‘context-sensitive’ help (Notes client)
Mostly providing a help function in a database is a step that comes long after an application has been put ‘live’.
In order to save myself a lot of design additions I created a context-sensitive ‘help’ function for application managers so they can add them later, when users come with the need for documentation.
The function I add on view- and formlevel wherever I think it might be handy in the future and is really easy to implement.
On a view or a form I add an action button with some @formula code.
On a view:
db:=@Subset(@DbName; -1);
@If(@IsError(@DbLookup(”":”NoCache”;”"; “(Help)”; @ViewTitle+”\\”+db;1));
@If(@IsMember(”[Admin]“;@UserRoles);
@Do(
@Set(”tmpval”;@Prompt([YesNo];”Help module”;”There is no help for this view available. Would you like to create a new Help document?”));
@If(tmpval;@Command([ToolsRunMacro];”(CreateHelpView)”);”"));
@Prompt([Ok];”Help module”;”There is no help for this view available.”));
@Command([OpenHelpDocument]; “”; “(Help)”; @ViewTitle+”\\”+db))
On a form:
db:=@Subset(@DbName; -1);
@If(@IsError(@DbLookup(”":”NoCache”;”"; “(Help)”; Form +”\\”+db;1));
@If(@IsMember(”[Admin]“;@UserRoles);
@Do(
@Set(”tmpval”;@Prompt([YesNo];”Help module”;”There is no help for this form available. Would you like to create a new Help document?”));
@If(tmpval;@Command([ToolsRunMacro];”(CreateHelpDoc)”);”"));
@Prompt([Ok];”Help module”;”There is no help for this form available.”));
@Command([OpenHelpDocument]; “”; “(Help)”; Form+”\\”+db))
If a user has the role Admin assigned it will enable to create a new ‘Help’ document for this particular design element.
If there is no ‘Help’ document available for this design element a popup window will appear:

When selecting ‘No’ the code will stop. When selecting ‘Yes’ an agent will run and creates the document on the background. A confirmation will appear:

If the user hits the action button again the newly created document will appear in a new window, just like the normal client Help function would do. This enables the user to switch between the application and the ‘Help’ document.
If you add the Admin role in an Authors field on the ‘Help’ document they can edit the document via a double-click and add content to the document.
At least this function saved me a lot of time and surprised application managers that it was already there from scratch!
Here is the code for the agent:
Sub Initialize
%REM
———————————————————————————————————————
The next script runs when there is no help document available for this design element
The script will create a new help document
———————————————————————————————————————
%END REM
Dim Session As New NotesSession
Dim workspace As New NotesUIWorkspace
Dim db As NotesDatabase
Set db = session.CurrentDatabase
Dim lookupview As NotesView
Dim uidoc As NotesUidocument
Dim thisdoc As notesdocument
Dim lookupdoc As notesdocument
Dim kolldoc As notesdocument
Dim kollview As notesview
Dim newhelpdoc As notesdocument
Set uidoc = workspace.CurrentDocument
Set thisdoc = uidoc.Document
Dim helpform As String
Dim server As String
Dim path As String
path = db.FilePath
helpform=thisdoc.Form(0)
’Collect data from help view
Set kollview = db.GetView( “(Help)” )
key$ = helpform+” “+path
Set kolldoc = kollview.getdocumentbykey(key$, True)
If kolldoc Is Nothing Then
’Create a new help document
Set newhelpdoc = db.CreateDocument
newhelpdoc.form=”F-Help”
newhelpdoc.Tx_About=”Instructions for: ” + helpform
newhelpdoc.Tx_Databas=path
newhelpdoc.Tx_Form=helpform
saveres = newhelpdoc.save(True,True)
If saveres Then
var = Messagebox(”A help document has been created in the Help module.”+Chr(10)+Chr(10)+”Please test the connection via the Help button.”,0+64,”Help module”)
Else
var = Messagebox(”A Help document could not be created. Please check the connection to the Help module.”,0+16,”Help module”)
End If
Else
var = Messagebox(”A Help document already exists for this form.”,0+16,”Help module”)
End If
End Sub
Re: Collapse a categorized embedded View by default
I have received some questions how to implement the functionality to collapse an embedded categorized view by default.
I think it would be most easy to add some screendumps, so ‘happy typing’ wished!

Figure 1. Globals declared

Figure 2. Postopen event on Form with embedded View

Figure 3. Code under Actionbutton

Figure 4. Collapse function