First Shot at Data a Visualization with Tableau
Learn About Tableau
todays bird

shark vs the universe
Lint Roller? I Barely Know Her
Show & Tell
Claire Keane

❣ Chile in a Photography ❣
dirt enthusiast
sheepfilms
Misplaced Lens Cap
Today's Document
2025 on Tumblr: Trends That Defined the Year

Origami Around

blake kathryn
AnasAbdin
Sade Olutola
noise dept.
Mike Driver

Kaledo Art

Love Begins
seen from India

seen from United States

seen from Malaysia

seen from United States
seen from India

seen from United States

seen from United States

seen from Malaysia

seen from Malaysia
seen from United States
seen from T1

seen from Armenia

seen from United States
seen from United States
seen from Ukraine
seen from United States
seen from Germany

seen from United States
seen from Singapore
seen from Guernsey
@lotuspizza
First Shot at Data a Visualization with Tableau
Learn About Tableau

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
Hidden Fields in the Lotus Notes Client
Here's a little trick I've been doing for a while now. Often to carry out system support I'll need to check the value of a field that is hidden from the user interface. There is the old faithful Document Properties dialog box, but it is a little fiddly, tiny text and viewable area and little scroll buttons.
So now I do this. I have my system fields at the top of a form. I lay them out in a table, label them and add any context I need, and then group them together in ways that makes sense for their functional use. By default this top of the form is hidden, but a I have a form action (displayed only for an administration role) that toggles the section open and closed. You can see the form action in the far right of the button bar, its just an icon with some arrows.
The hidden fields section in its default closed state.
The open hidden fields section.
The code to do this is pretty basic. There's a number field hidden above the tables of system fields. It defaults to 1 (which is @True). It is also reset to a value of 1 on QueryOpen.
Call source.Document.ReplaceItemValue("HideAdvanced", 1)
The hidden fields section in design mode.
The buttons with the expander icons (the arrows down and arrows up) are then set to hide based on the value of HideAdvanced, the field. I also have a check for a user role @Contains(@UserRoles; "SystemAdmin"). The buttons simply hide or show the table by changing the value of the HideAdvanced field and then refeshing the document.
'show advanced fields Call docThis.ReplaceItemValue("HideAdvanced", 0) Call uidThis.RefreshHideFormulas 'hide advanced fields Call docThis.ReplaceItemValue("HideAdvanced", 1) Call uidThis.RefreshHideFormulas
Domino Views Using Dojo XHR - Part 3
Finally some full code! Everything else has seemed so long that I felt it would look silly in a post. This just scrapes in - but still 260 lines! That being said it still includes a few libraries, so it's not complete as it stands here.
All this stuff is for an application of course, I'm trying to build as much of a re-usable framework as possible, but as I post things up I notice application specific terms, or worse!, hard coded elements (they might makes sense in the context of the application but not for a general release). Once it is looking more like a real framework, that might have a more general appeal, I'll post some NSF files and perhaps LSS text files.
This is the agent that is called to generate JSON that works in a Dojo DataGrid. It's mentioned in http://lotuspizza.tumblr.com/post/33732912335/domino-views-using-dojo-xhr-part-2 and is referenced and used by some generated Javascript to create a Dojo ItemFileReadStore. I wanted to get the Dojo DataGrid working, although it does looks as though it might be now deprecated, and struggled with the antive Notes JSON. Far easier to write my own I thought and it also gives me the flexibility of being able to add some user configurable options if I need.
Web JSON View Agent (aliased as json-view)
Option Public Option Declare Use "Web Utils" Sub Initialize On Error GoTo ErrorHandler Dim session As New NotesSession Dim dbThis As NotesDatabase Dim dbTarget As NotesDatabase Dim vwTarget As NotesView Dim docContext As NotesDocument Dim docFormConfig As NotesDocument Dim objView As WebElement Dim vJSON As Variant Dim sViewName As String Set docContext = Common_Session.DocumentContext If Not IsList(QUERY_STRING_LIST) Then 'TODO return something in the stream so an error can be handled Exit Sub End If 'get the view to display If IsElement(QUERY_STRING_LIST("v")) Then Set docFormConfig = GetFormConfig(QUERY_STRING_LIST("v")) 'only one dojo object returned for view Set objView = GetTabObjects(docFormConfig) sViewName = objView.ConfigDoc.GetItemValue("ViewTitle")(0) 'get the database and view objects Set dbTarget = GetViewTargetDatabase(objView) Set vwTarget = dbTarget.GetView(sViewName) Else 'return something in the stream so a non handled report type error can be processed Exit Sub End If If IsElement(QUERY_STRING_LIST("test")) Then Print |<h1>| & objView.Title & |</h1> End If vJSON = OutputView(vwTarget, objView) If IsElement(QUERY_STRING_LIST("test")) Then Print (vJSON) Else Print( |Content-type: application/json| ) Print( || ) Print( CStr(vJSON) ) End If Exit Sub ErrorHandler: Call LogError Print |<h2>Error</h2> & <p>Error at line <strong>| &_ Erl & |</strong> of <strong>| & GetThreadInfo(1) & |</strong> : <em>| & Err & | | & Error(Err) & |</em></p>| Exit Sub End Sub
OutputView Function
This function is what traverses the entries in the view. There are a few options for processing - such as whether the entry pointer should start at the top or the bottom of the view. It can also start the entry pointer at an index (passed in the URL parameters) and it handles categorised selections in a view using this index.
%REM Function OutputView Description: Comments for Function %END REM Function OutputView(vwTarget As NotesView, objView As WebElement) As Variant On Error GoTo ErrorHandler Dim navTarget As NotesViewNavigator Dim vecTarget As NotesViewEntryCollection Dim entCurrent As NotesViewEntry Dim vJSON As Variant Dim vColumns As Variant Dim gTargetCount As Long Dim gRowCount As Long Dim gIndex As Long Dim iDisplayMax As Integer Dim sIndex As String Dim bInvertSort As Boolean Dim bCategorised As Boolean Dim bRefresh As Boolean 'check if there is a count If IsElement(QUERY_STRING_LIST("count")) Then iDisplayMax = CInt(QUERY_STRING_LIST("count")) End If 'check if the view sort should be inverted If IsElement(QUERY_STRING_LIST("sort")) Then If QUERY_STRING_LIST("sort") = "1" Then bInvertSort = True End If End If 'check for a starting index - UNLIKELY If IsElement(QUERY_STRING_LIST("i")) Then sIndex = QUERY_STRING_LIST("i") End If 'check if the view should be refreshed first - can take a little time If bRefresh Then Call vwTarget.Refresh() End If If sIndex = "" Then 'no field key = show all entries Set vecTarget = vwTarget.AllEntries gTargetCount = vecTarget.Count Else 'views can be processed using either viewnavigator (for categorised views) or view entry collections If objView.ConfigDoc.Categorised(0) = "1" Then bCategorised = True Set navTarget = vwTarget.CreateViewNavFromCategory(sIndex) gTargetCount = navTarget.Count Else Set vecTarget = vwTarget.GetAllEntriesByKey(sIndex) gTargetCount = vecTarget.Count End If End If 'if not currently traversing a view get the start entry If gIndex = 0 Then 'check for direction of browsing If bInvertSort Then gIndex = gTargetCount Else gIndex = 1 End If End If If gTargetCount = 0 Then OutputView = |gTargetCount = 0| Exit Function End If 'get the first document to write from If bCategorised Then Set entCurrent = navTarget.GetNth(gIndex) Else Set entCurrent = vecTarget.GetNthEntry(gIndex) End If 'get output columns vColumns = objView.ConfigDoc.GetItemValue("Columns") 'start the json vJSON = vJSON & |{"identifier": "row",| vJSON = vJSON & |"items": [| While Not entCurrent Is Nothing 'start the row vJSON = vJSON & || 'get all the column values for the row vJSON = vJSON & WriteViewEntry(vwTarget, entCurrent, objView, vColumns, gRowCount) gRowCount = gRowCount + 1 If iDisplayMax <> 0 And gRowCount = iDisplayMax Then Set entCurrent = Nothing Else 'TODO include memory check? variable size in javascript and in domino 'check for direction of browsing If bInvertSort Then If bCategorised Then Set entCurrent = navTarget.GetPrev(entCurrent) Else Set entCurrent = vecTarget.GetPrevEntry(entCurrent) End If Else If bCategorised Then Set entCurrent = navTarget.GetNext(entCurrent) Else Set entCurrent = vecTarget.GetNextEntry(entCurrent) End If End If End If 'end the row vJSON = vJSON & || Wend 'remove the final comma seperator vJSON = Left(vJSON, Len(vJSON) - 1) & |]}| OutputView = vJSON Exit Function ErrorHandler: Call LogError Exit Function End Function %REM Function WriteViewEntry %END REM
OutputView Function
The actual output is handled in this function
Function WriteViewEntry(vwTarget As NotesView, entCurrent As NotesViewEntry, objView As WebElement, vColumns As Variant, gRow As Long) As String On Error GoTo ErrorHandler Dim iCol As Integer Dim sJSON As String Dim sColValue As String Dim sTitle As String Dim bLongFormat As Boolean If bLongFormat Then sJSON = sJSON & |{"row":|& gRow & |, "cols":[| For iCol = LBound(vColumns) To UBound(vColumns) sColValue = FormatViewEntryCol(entCurrent, CInt(vColumns(iCol)), True) sTitle = vwTarget.Columns(CInt(vColumns(iCol))).Title sJSON = sJSON & |{"title":"| & sTitle & |","value":"| & sColValue & |"},| Next sJSON = Left(sJSON, Len(sJSON) - 1) & |]},| Else sJSON = sJSON & |{"row":|& gRow & |,| For iCol = LBound(vColumns) To UBound(vColumns) sColValue = FormatViewEntryCol(entCurrent, CInt(vColumns(iCol)), True) sJSON = sJSON & |"col| & CInt(vColumns(iCol)) & |":"| & sColValue & |",| Next sJSON = Left(sJSON, Len(sJSON) - 1) & |},| End If 'return WriteViewEntry = sJSON Exit Function ErrorHandler: Call LogError Exit Function End Function
FormatViewEntryColFunction
This does a few important entity replacements with a companion function called CleanHTML, I'd expect a few more entities to be added over time. It also handles data type formatting, currently just dates but I can imagine this needing to be extended over time.
Function FormatViewEntryCol(entCurrent As NotesViewEntry, iCol As Integer, bJavaScript As Boolean) As String On Error GoTo ErrorHandler Dim vwTarget As NotesView Dim vValue As Variant Set vwTarget = entCurrent.Parent If iCol < vwTarget.ColumnCount Then vValue = entCurrent.Columnvalues(iCol) Else 'column is higher than the views column count Exit Function End If If IsArray(vValue) Then FormatViewEntryCol = CleanHTML(vValue, bJavaScript) Exit Function End If Select Case TypeName(vValue) Case "DATE" FormatViewEntryCol = Format(vValue,"d/m/yy h:nn") Exit Function End Select 'replace a few key characters with entities FormatViewEntryCol = CleanHTML(CStr(vValue), bJavaScript) Exit Function ErrorHandler: Call LogError() Exit Function End Function %REM Function CleanHTML Description: Comments for Function bJavascript = for javascript output %END REM Function CleanHTML(vHTML As Variant, bJavascript As Boolean) As Variant On Error GoTo ErrorHandler Dim vFind(9) As Variant Dim vReplace(9) As Variant Dim iCount As Integer Dim sCleanHTML As String vFind(0) = Chr(13) vFind(1) = Chr(10) vFind(2) = Chr(10) & Chr(13) vFind(3) = Chr(13) & Chr(10) vFind(4) = |"| vFind(5) = |'| vFind(6) =|[| vFind(7) = |]| vFind(8) = |{| vFind(9) = |}| vReplace(0) = "<br />" vReplace(1) = "<br />" vReplace(2) = "<br />" vReplace(3) = "<br />" vReplace(4) = |"| vReplace(5) = |'| vReplace(6) = |[| vReplace(7) = || vReplace(8) = |{| vReplace(9) = |}| If IsArray(vHTML) Then For iCount = LBound(vHTML) To UBound(vHTML) vHTML(iCount) = Replace(vHTML(iCount), vFind, vReplace) Next 'there's an option here to implode sCleanHTML = Join(vHTML) Else sCleanHTML = Replace(vHTML, vFind, vReplace) End If CleanHTML = sCleanHTML Exit Function ErrorHandler: Call LogError() Exit Function End Function
Domino Views Using Dojo XHR - Part 2
Here's a function that writes out the Javascript to present a view in a DojoGrid using a custom JSON format. It gets a whole bunch of stuff (such as the columns to output) from a web config document. There's a convenience class* that holds these config documents and calculates some class properties from the configuration settings.
This Function is in a library that includes a few other utility types LotusScript libraries*, so there a re some Global objects floating around in there (such as the NotesSession.DocumentContext).
At a high level the code flows like this. The browser requests a resource that has a Notes Form as part of its design elements. The form has a Query Open agents (and a bunch of items* most importantly an item that can present some computed Javascript on the rendered Form. As the form renders the QueryOpen agent writes Javascript, using in part the code below. This Javsacript makes a call to a Notes agent with a view name as a QueryString parameter. This agent returns the view JSON in a nice format for the Dojo DataGrid to render. Phew.
* All these to be documented in further posts
Function ProcessViewGrid(objView As WebElement) As Variant On Error GoTo ErrorHandler Dim dbTarget As NotesDatabase Dim vwTarget As NotesView Dim vJavascript As Variant Dim vColumnTitles As Variant Dim vColumnWidths As Variant Dim vColumns As Variant Dim iCount As Integer Dim sViewName As String Dim n As String n = Chr(13) vColumns = objView.ConfigDoc.GetItemValue("Columns") sViewName = objView.ConfigDoc.GetItemValue("ViewTitle")(0) 'get the database and view objects Set dbTarget = GetViewTargetDatabase(objView) Set vwTarget = dbTarget.GetView(sViewName) vColumnTitles = GetViewColumnTitles(objView, vwTarget) vColumnWidths = GetViewColumnWidths(objView, vwTarget) vJavascript = |require(["dojox/grid/DataGrid", "dojo/data/ItemFileReadStore", "dojo/dom"]);| & n vJavascript = vJavascript & |var grid, store;| & n vJavascript = vJavascript & |dojo.ready(function(){| & n vJavascript = vJavascript & | store = new dojo.data.ItemFileReadStore({| & n vJavascript = vJavascript & | url: 'http://' + server + '/' + dbpath + '/json-view?OpenAgent&v=| & CONTEXT.view_title(0) & |&sort=1&count=10'| & n vJavascript = vJavascript & | });| & n vJavascript = vJavascript & |var layout = [[| & n 'get columns from config For iCount = LBound(vColumns) To UBound(vColumns) vJavascript = vJavascript & | {name:'| & vColumnTitles(iCount) & |',| vJavascript = vJavascript & | field:'col| & vColumns(iCount) & |',| vJavascript = vJavascript & | width:'| & vColumnWidths(iCount) & |'},| & n Next vJavascript = vJavascript & | ]];| & n vJavascript = vJavascript & |grid = new dojox.grid.DataGrid({| & n vJavascript = vJavascript & | store: store,| & n vJavascript = vJavascript & | query: { row: "*" },| & n vJavascript = vJavascript & | structure: layout| & n vJavascript = vJavascript & | });| & n vJavascript = vJavascript & | // since we created this grid programmatically, call startup to render it| & n vJavascript = vJavascript & | grid.placeAt("view-listing");| & n vJavascript = vJavascript & | grid.startup();| & n vJavascript = vJavascript & |});| ProcessViewGrid = vJavascript Exit Function ErrorHandler: Call LogError Exit Function End Function
Domino Views Using Dojo XHR - Part 1
The little XPages training I've done have left me convinced it has only limited specific uses - in my mind upgrading interfaces for existing apps. Also you have to be prepared to leave web standards at the door. I like standards though. This means that I try to get native Notes to work with real world web frameworks.
Early on I gave this thing a bit of a go, getting some views out of Domino in a simpler JSON format than the native. Basically I wanted to be able to get domino views working in the Dojo framework without any XPages overhead. It’s a fairly wide solution, by that I mean it involves quiet a few design elements. I’ll try to document and discuss them all here. Off the top of my head there are:
A form to handle the display
A QueryOpen agent
A config system to handles special cases for views and display
A few LotusScript libraries for common functions
Some static Javascript and some that is generated by the QueryOpen code
Here’s a screen (below) of the config documents that help define the output for a view embedded in a Dojo tab. Most important (about from the target of the view) is the settings that define what columns to output and what HTML to wrap around them. I’ve set a default of a <div> for each “row” and a <span> for each “column”, these settings override that default however.

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
Error Handling
A quick note about error handling. I use Julian Robichaux's OpenLog for pretty much all my error handling.
http://www.openntf.org/internal/home.nsf/project.xsp?action=openDocument&name=OpenLog
I've done a few minor modifications to it, mostly around interface, but also on the notifications system. I really should see about submitting my changes back to the project...
WHAT IS YOUR FAVORITE INANIMATE OBJECT?
i quite like drums. they do bounce about a bit while been played tho.
Query String as a Global List
I have this little trick in a script library for web utilities. A global object for the session DocumentContext and a List for all the elements in the URL QueryString.
Declarations
Dim CONTEXT As NotesDocument Dim QUERY_STRING_LIST As Variant
Initialize
Dim s As New NotesSession If Not s.DocumentContext Is Nothing Then Set CONTEXT = s.DocumentContext QUERY_STRING_LIST = GetQueryStringList(CONTEXT) End If
The QueryString Handling Function
%REM Function GetQueryStringList Description: Comments for Function TODO: possibly handle multiple occurences of parameters somehow? * either make some kind of array or handle to take the first only * currently would process only the last occurence %END REM Function GetQueryStringList(docCurrent As NotesDocument) As Variant On Error GoTo ErrorHandler Dim sQueryStringList List As String Dim vElements As Variant Dim vElement As variant Dim sQueryString As String sQueryString = docCurrent.QUERY_STRING(0) 'check there are some parameters in the query string If InStr(sQueryString, "&") > 0 Then 'split the parameters into an array sQueryString = Right(sQueryString, Len(sQueryString) - InStr(sQueryString, "&")) vElements = Split(sQueryString, "&") 'check a parameter has a value element If Not IsNull(vElements) Then ForAll element In vElements If InStr(element, "=") > 0 Then 'add the value to the list element vElement = Split(element, "=") sQueryStringList(vElement(0)) = vElement(1) Else 'just add a list element - TODO might be nice to add as boolean true 'currently use IsElement(QUERY_STRING_LIST("test")) sQueryStringList(element) = "" End If End ForAll End If Else GetQueryStringList = Null Exit Function End If GetQueryStringList = sQueryStringList Exit Function ErrorHandler: Call LogError() Exit Function End Function
Globals and Some Standards and Considerations
One of my standards is to CAPITALISE anything I declare globally. I also use underscore_separation rather than CamelCasing for global variables. It helps make them stand out in code on a first glance.
Another standard I have around global variables is to be a little picky about where I declare them. My general preference is to have a LotusScript library to hold them that included just about everywhere, but some are so specific in scope that I might just store them with in a library or agent.
I will often use global constants in place of checking for aliased integers from forms or CGI actions. Consider a radio button set that has a friendly text for users but returns 0, 1, 2 or 3. I would consider setting up some constant string with names that imply the values of the radio options. For example I would set up Const OPTION_UPDATE$=”1” and be checking for a value of OPTION_UPDATE in code for the radio option “Update all actions with this new value|1”. In a URL QueryString I often have an action value to determine what should happen in an open or save event. Again here I would have Global Constants with meaningful variable names. Things like ACTION_NO_SAVE or ACTION_UPDATE_ALL tell the whole story, it’s still important to comment sections of code of course, but a meaningful name goes a long way.
These simple standards help with code readability and mean that you are often unlikely to need to open the a bunch of associated design elements for the simple context of field values or action parameters.
a common validation 'does it have a value' function
This is one of my common validation libraries. It tries to check if anything has a value and I use it all over the place, throwing all kinds of weird things from my own and inherited code at it. As a result it went through a lot of debugging to get where it is both accurate and robust.
Function ValidateHasValue(vItemValue As Variant) As Boolean On Error GoTo ErrorHandler Dim vHoldItemValue As Variant 'store the item value as it gets modified during validation If IsObject(vItemValue) Then Set vHoldItemValue = vItemValue Else vHoldItemValue = vItemValue End If 'if value is an array check it has a value '* this should be extended to check all elements until a value is found '- or a fulltrim and resursive call? If IsArray(vItemValue) Then If Not IsObject(vItemValue(LBound(vItemValue))) Then 'check for an array of more than one element If UBound(vItemValue) - LBound(vItemValue) > 0 Then vItemValue = CleanItemValue(FullTrim(vItemValue)) Else If Len(CStr(vItemValue(LBound(vItemValue)))) > 0 Then ValidateHasValue = True vItemValue = vHoldItemValue Exit Function End If End If Else 'an array of product objects - it can happen If vItemValue(LBound(vItemValue)) Is Nothing Then ValidateHasValue = false vItemValue = vHoldItemValue Exit Function Else ValidateHasValue = True vItemValue = vHoldItemValue Exit Function End If End If End If 'an uninitialized array If IsEmpty(vItemValue) Then ValidateHasValue = False vItemValue = vHoldItemValue Exit Function End If 'null variable If IsNull(vItemValue) Then ValidateHasValue = False vItemValue = vHoldItemValue Exit Function End If 'product object If IsObject(vItemValue) Then If Not vItemValue Is Nothing Then ValidateHasValue = True Set vItemValue = vHoldItemValue Exit Function End If End If 'check for value of no length - messy nested if's from much debugging If TypeName(vItemValue) = |"STRING"| Then If Len(vItemValue) > 0 Then ValidateHasValue = True Else ValidateHasValue = False End If Else If IsScalar(vItemValue) Then If Len(CStr(vItemValue)) > 0 Then ValidateHasValue = True End If Else vItemValue= FullTrim(vItemValue) If Len(CStr(vItemValue(UBound(vItemValue)))) > 0 Then ValidateHasValue = True Else ValidateHasValue = False End If End If End If vItemValue = vHoldItemValue Exit Function ErrorHandler: Call LogError Exit Function End Function
The CleanItemValue function is there to handle arrays, specifically, uninitialised arrays.
%REM handle field values with one array element i.e FieldName(0) %END REM Function CleanItemValue(vItemValue As Variant) As Variant On Error GoTo ErrorHandler On Error 200 GoTo UninitializedArrayHandler 'handle field values with one array element i.e FieldName(0) If IsArray(vItemValue) Then 'check for an array of one element If UBound(vItemValue) - LBound(vItemValue) = 0 Then 'return the value of the single array element as a variant If IsObject(vItemValue) Then Set CleanItemValue = vItemValue(LBound(vItemValue)) Else CleanItemValue = vItemValue(LBound(vItemValue)) End If Exit Function End If End If CleanItemValue = vItemValue Exit Function ErrorHandler: Call LogError Exit Function UninitializedArrayHandler: Call LogError CleanItemValue = null Exit Function End Function

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
A common NotesDocument selector for UI driven code
I always want to have fairly universal code for UI initiated events wherever they fired from, it just makes things a bit easier most of the time. I especially like selection code that handles just an active document in a view, without the entry needing to be ticked. It makes sense to me that if I've clicked on an entry I should be able to do something with it without needing to tick again. Users are quite often confused by this also.
This is what I use most of the time. A user clicks on a button somewhere (or selects an agent from the actions menu) in an action bar on a document or similar form action, or in a view action - either if a document is active in the view or if more than one document is checked in the view.
Dim session As New NotesSession Dim workspace As New NotesUIWorkspace Dim colSelected As NotesDocumentCollection Dim docCurrent As NotesDocument Dim bMulti As Boolean 'determine if single doc or multiple docs are selected in a view or if UI doc is open If workspace.CurrentView Is Nothing Then 'if there is no view then access the backend doc thru the uidoc Set docCurrent = workspace.CurrentDocument.Document Else 'more than one doc may be selected if there is a view If workspace.CurrentView.Documents.Count > 0 Then Set colSelected = workspace.CurrentView.Documents Set docCurrent = colSelected.GetFirstDocument bMulti = True Else> Set docCurrent = session.DocumentContext End If End If
From here I go and run the main bit of my code:
If bMulti Then Print "Processing status for " & colSelected.Count & " records ..." While Not docCurrent Is Nothing 'process the current document Set docCurrent = colSelected.GetNextDocument(docCurrent) Wend Else 'process just the one document End If