Integrating FormCheck and Domino forms

I have been working with the MooTools framework for the past week or so.  I am realyl starting to like not having to code everything by hand and am thinking it might save quite a bit of labor in the near future.  A tool that extends the MooTools 1.2.1 framework is FormCheck.  It makes client side validation of forms an absolute snap.

While everything worked great in IE 7, I was having serious issues getting the FormCheck code to fire when working in FireFox 3.  After lots of testing and beating my head against the keyboard, I figured out that the problem was caused by Domino not generating an ID attribute for the FORM tag.  I was able to finally work around it by setting the ID to be the same value as NAME before trying to validate the form. 

To me, this is an extra step that shouldn't have to be done by deverlopers.  I have created an ideaJam entry to have the Domino server do this automatically.

LS Function to get ACL entries have have a role enabled

Here's a simple LS function that returns a list of ACL entries that have a specific role enabled:

Function GetEntriesByRole(rolestring As String) As String
  Dim session As New NotesSession
  Dim db As NotesDatabase
  Dim acl As NotesACL
  Dim aclentry As NotesACLEntry
  
  On Error Goto errorproc
  
  Set db = session.CurrentDatabase
  Set acl = db.ACL
  Set aclentry = acl.GetFirstEntry
  
  While Not(aclentry Is Nothing)
  If aclentry.IsRoleEnabled(rolestring) Then
    If GetEntriesByRole = "" Then
     GetEntriesByRole = aclentry.Name
    Else
     GetEntriesByRole = GetEntriesByRole & "~|~" & aclentry.Name
    End If
   End If
   
   Set aclentry = acl.GetNextEntry(aclentry)
  Wend
  
finish:
  Exit Function
  
errorproc:
  Call LogError()
  Resume finish
 End Function

The list of acl entries is returned in a string that uses "~|~" to separate the multiple entries.  You can use the Split function to expand the list into an array.  The role passed must contain the [] charaters.

ASND Export Facility 2.0.2 Released

I have just posted the 2.0.2 version of the ASND Export Facility.  This version fixes a number of really, silly bugs.  I wish I had more time to do testing the right way, but such is life when you are writing code for free.

There is one bug that is currently unresolved that I would love some help on.  The crux of the problem is this:  When doing sorting a collection by multiple fields, the secondary sorts are opposite what they should be.  For example, if I want to sort the results by Person in ascending order and then by Date in descending order, the results are return with Person in ascending order and Date in ascending order.  This did not happen when I was using a Bubble Sort, so I must have screwed up something in the Merge Sort code.  If you have experience with these things and want to take a crack at solving the problem, by all means download the latest release and take a look at my spaghetti code.

ASND Export Facility 2.0 Released

ASND Designs has released the latest version of the ASND Export Facility on OpenNTFVersion 2.0.1 makes exporting on and writing reports against Domino databases easier than ever before.  The new release include the following enhancements:

  • ASND Web Report Framework - Imagine being able to easily create web based reports that allow users to determine the parameters of the report and even the data returned in the report.  V2.0 includes 3 real world sample reports that combine AJAX and the ASND reporting engine to deliver reports in HTML, Microsoft Word, and Microsoft Excel formats via the browser.
    • ASND Name Search - searches all addressbooks on the server for a name or part of a name
    • ASND Domlog Report - reports on the activity stored within the domlog.nsf and can be limited to a specific database or set of databases.  Users can choose which fields to return and how to sort the report.
    • ASND Sametime Activity Report - reports on either the last time a user logs into Sametime or all of the login failures from the stlog.nsf.  Report can be limited to a specific period of dates.
  • ASNDDocumentCollection Script Library - a portable script library that you can drop into any database for collecting and sorting data from Notes Databases without the need for additional views.
  • MergeSort Sorting Routine - all sorts will now be done using a MergeSort function.  Performance of existing reports should be exponentially better, especially when sorting large data sets multiple times.
  • Full Text Exports - Regular Exports can now be configured to use either Selection Formulas or a Full Text Search to collect the documents.  Both choices can still be done against multiple databases.

Update:  2.0.1 has been released to fix 2 major bugs in the sorting routines and include the image used in the web reports.

ASNDSort Library on OpenNTF

As a Notes developer, I have never been really interested in sorting algorithms.  For me, I just want to sort something ascending or descending and I don't really care how it works or which sort routine is better suited for a given task.  That being said, from time to time, I have had to delve into the world of sorting and have compiled a LSS file that contains 3 of the more popular sorting routines: Bubble Sort, Quick Sort, and Merge Sort.  This LSS file is now available in the Code Bin on OpenNTF.org.

No more will I have to go searching through my collection of past databases to find that one sort routine that sorts DateTime values.  No I can just include the LSS file and easily select which algorithm I want to use and the type of data being sorted and get my results with no muss, no fuss.  Just the way I like it!

ASND Export Facility a big hit

There is no better feeling for a developer than to have a user heap praise on you for something that you have built.  Such is the case recently with the ASND Export Facility.

I knew I had scored big time when I got an IM in about 15 minutes from her ... "This is so freaking cool!" She had promised the reports to the user by November 30th if at all possible. With ASND Export Facility, she handed them over a day later and left everyone in awe of her technical prowess.

The really cool thing is that this quote comes from an article entitled ASND Export Facility Rocks! in LotusUserGroup.org Developer Tips December 2007.  Go over and read everything that Duffbert has to say about the application.

OpenNTF Applications Upgrades

I am in the process of adding some things to my OpenNTF applications and was wondering what designer client I should be using to do the changes.  I think that I can safely move off of the Notes 6.5.x client, but is it ok to do all my coding in the Notes 8 Designer?  I don't think that I will be doing anything that will cause issues, but I do wonder how other project chef's are handling the multiple supported versions of Notes.

Need Help with Extended ASCII Characters and Javascript/AJAX

Being from the United States, I don't run into the need to deal with Extended ASCII characters very often.  In reality, I never deal with them.  So I am pretty clueless as to how to solve the current issue I am having on Idea Jam.

The Idea Jam registration form is submitted via AJAX and works extremely well.  However, we have run into a problem where any extended ASCII characters aren't handled correctly on by the LotusScript agent that performs the registration.  The problem is that those characters are actually sent as 2 characters instead of one.  For example, é is sent as ├® or encoded, using encodeURIComponent(), to %C3%A9.  The code I am currently using to decode the encoded characters doesn't handle the double values.

Does anyone have any code that I can look at/borrow that translates encoded extended ascii characters and can still handle encoded spaces and ampersands?  Any help would be greatly appreciated.

Can’t get the footer out of my document’s back end

A task came to me recently asking for some changes to a report that we generate from a Domino site. While most of the changes were sort of minor, there was one that gave me pause. The users wanted to add a footer to the report when it is exported to Word. Now, if this was a request to be done via the Notes client, it would be a snap to add the footer via COM. That might even be a possibility if Word was installed on the server, but it's not. So the only thing I have available is standard html.

Following some hints I found on the Internet, I was able to get a footer to show up like I wanted.  Unfortunately, there was one major side effect, namely that the footer printed twice on the last page, once as the footer and once at the end of the text.  When I looked at the source in Word, I could see that it moved the code for the footer out into a separate HTML file, just like it would if you did a Save as HTML from Word.  But for some reason, it left the footer code on the back end of the main document.  Very frustrating.

Has anyone else been able to successfully get a footer on a Word form when the document is created using only HTML?  Or is this simply a bug in the way Word handles HTML?  Inquiring minds want to know.

DatersBlueBook.com is live

Chris and I have been having some fun recently coming up with ideas for Domino sites that you aren't likely to build in your shirt and tie day job. The first of these sites has gone live and is called DatersBlueBook.com. The site itself is another in a long line of sites that allow you to take a quiz and post a badge on your site. Our quizzes are going to be based on dating and relationships and will hopefully make our users laugh a little. While the site itself isn't that novel, I would like to share some of the method we have used to create the site in Domino.

First, I have implemented the YUI CSS Foundation framework. This cross browser UI framework allows me to accurately position dynamic elements on the site as well as being able to set font sizes. The YUI Grid Builder is great for generating your basic site layout. Unless you are planning on doing something really wild, this builder will do everything you need get your stuff in the right place. YUI Fonts allows me to have consistent sizing and line-heights across all browsers. And YUI Reset sets the default styling of HTML elements to be the same across all browsers. And if that wasn't enough, the entire CSS file is hosted on a Yahoo server, so it doesn't impact my bandwidth and enables caching across multiple sites.

Something else that we implemented was the idea of using HTML generated from views to build our HTML form. For each quiz, there are a number of question documents on the back end. I use a view to sort them correctly, put them into specific categories on the page, and generate the entire HTML for the question. That is pretty straight forward, but I took it a step further. I used a ComputedText element inside a JavaScript function on the page to generate my validation routine for each field that was required. What these 2 things mean is that I never have to touch the design of the database whenever I add/change/delete quizzes.

The final little tidbit of interesting coding has to do with the way the Grade page is generated. We have all used Print statements in LotusScript agents to spit out HTML for our sites. But if you look at the way the Grade Page and Badge is generated, all you see is a SCRIPT tag with a SRC attribute pointing to an agent. What the agent does is wrap all the HTML it generates in document.write functions. Exciting, heck no! Every other platform on the web does this also. But since we rarely build code that has to be run from web pages in different domains, it's not something we often have to worry about. This is how OpenNTF is able to put a badge on your blog and how Google is able to put ads on your site.

The site is far from complete and will continue to be refined over the next month or so, but it is interesting to me to try and see how Domino can be used in a non-traditional environment. Hopefully, I will have some time to make it a bit more graphically pleasing, but I wouldn't expect the Mona Lisa any time soon.


idea exchange, another Bruce project is born

Bruce Elgort has been using his time away from OpenNTF in ways that will keep him mostly out of trouble.  His latest venture is idea exchange. The idea behind this site is to give the IBM/Lotus community the ability to put forward ideas to improve the products that they use on a daily basis.  This could be an idea to improve a monster product like Notes/Domino/Webphere or an add-in product like TeamStudio's CIAO or even a specific application on OpenNTF or default template in Notes.  Heck, I can even envision a future where products outside the IBM/Lotus family are discussed.

Update: Bruce clarified the spacing and naming of the site.  :-)

Need a little testing help

I have run into an issue with the ASND Export Facility and Office 2007 and need a little assistance confirming the problem.  If you look at this discussion response, you will see the problem that Jan is running into.  What I need from a few members of the audience playing along at home is the following:

  1. I need computers with the following combinations of software:
    Notes 7.0.x and Office 2003
    Notes 6.5.x and Office 2007
    Notes 7.0.x and Office 2007
  2. Download the latest release of the Export Facility (1.2 RC).
  3. Create an export/report against a database that has zero-leading numbers stored as text, like id numbers or zip codes.
  4. Set the Export format to be Excel.
  5. Add that field as a column to an export and set the data type as text.
  6. Run the export.

If it works like it does on my work machine (Noes 6.5.x and Office 2003), Excel should show the leading zeros in the column and flag you to let you know that it's a number stored as text.  You can either send me an email/IM with your results or, better yet, leave a comment.

That’s weird, dude!

I know when I ask Julian and Rock about a problem I am having and they both respond "Weird!", that I am having an issue where my head might not be stuck in another orifice of my body.  The worst part about this problem is that it's simple functionality that Notes provides.

The issue I am having is with soft deletions, the soft delete view in particular.  After following the super simple steps to implement soft deletions in an existing database, I am able to delete documents and have them go to the trash view.  The issue comes when trying to restore the documents.  I have tried using both the @UndeleteDocument and @Command([EditRestore]) functions and have had mixed results with both.  They both always restore the document to the other views, but it's hit or miss as to whether or not the documents get removed from the trash view.  In thinking I had done something totally wrong, I have even copied the view and undelete agent from the OpenLog template, without any more success.  Rebuilding the view index using Shift-F9 does nothing. I have tried it locally, on a 6.5.5 server, and on a 7.0.2 server.  I have tried it on a new database created from a template and a copy of an existing db.

In searching the Internet and Notes Knowledgebase, I came up with nothing that matches my scenario.

Has anyone else come up against a problem like this in the past?  How did you solve it?

Mysterious NotesForm.Fields results

In the ASND Export Facility I have on OpenNTF, I use the Fields property of the NotesForm class to generate a list of fields that you can export from a selected database.  My assumption was that this property would return an array of all the names of the fields that were on the form.  This worked as expected until I got a bug report recently from a user who indicated that there were fields missing when you tried to export Contacts from a Personal Address Book.  For some reason, the Birthday field, along with a number of other fields, don't show up in the array returned by the Fields property.  I can't figure out any reason why they fields won't show up in the list.

Does anyone have any idea as to why certain fields don't show up in the fields array?  If I come up an answer, I will be sure to post it here.  Any help would be greatly appreciated.

ASND Export Facility 1.0 Beta Released

The latest version of the ASND Export Facility has been released on OpenNTF.  This new version has one major new feature and one minor one.  The minor feature is that you are now able to choose a dd/mm/yyyy format for date fields.  This should make the application more acceptable across the pond.  The major new feature is the ability to run a single report against multiple backend databases.  All of the documents from all the databases are collected and then sorted as one large collection before exporting in the desired format.  This is extremely helpful when reporting against multiple databases based on the same template, like Server logs or Address Books.  A good example might be a manager's progress report run against multiple project management applications.  Instead of having to run multiple reports and then consolidate them manually, users can now run one report in a matter of seconds and get all the information they need.

ASND Export Facility 0.9.81 Released
I got an email yesterday from Barbara in Switzerland (I think) alerting me to an issue in the way the ASND Export Facility handles exporting multiple values from a single field. The problem was it wasn't doing it at all. After a quick look at the code, I found the issue and uploaded a new version to OpenNTF. How cool is it that someone whom I've never met and that lives in another country actually uses my code and then is able to help me find a bug in it.
OpenLog 1.5 Beta is in the wild
For those of you who are currently using Julian's wonderful OpenLog application or just saw his session at Lotusphere, he has just released a new version on OpenNTF.  The most exciting new feature is the ability to capture and log JavaScript errors as well as LotusScript and Java errors.  And the JavaScript logging will work on Domino and non-Domino based HTML pages.  Knowing Julian as I do, I am sure the code is almost bullet proof and Beta tag is only there to sooth his nerves.  If you are currently using OpenLog, I suggest you get the new version and run it through the paces to see if it's up to snuff.  For those of you who have not used it yet, download OpenLog and see just how much easier life can be when all your script errors are logged in one place.
JavaScript Compression Tools
Does anyone out there have a favorite JavaScript Compression tool that they use on a regular basis?  If so, I'd love a recommendation.  Google returned a bunch of free tools, but I'd rather go with one that someone else has used with successfully.
OpenNTF can now start Kindergarten

Happy Birthday OpenNTF

5 years old and still growing and getting stronger.  Many thanks to Bruce for all his hard work in keeping the site running and promoting the hell out of it.  And be sure to check out all of my ASND applications available for download.

ASND Export Facility 0.9.5 Released

I have released a new version of the ASND Export Facility on OpenNTF.  The new version doesn't change much as far as the end results are concerned, but the code under the hood has been re-written using a couple of custom LotusScript Classes.  The major benefit of the change is the fact that I am able to do multi-level sorting without relying on Excel to do it for me.  That means that the sorting is available for the Delimited Text exports.  When creating the export, you select the column to sort on, whether to sort ascending or descending, and the data type of the column being sorted. 

I had been wanting to do this for quite a while, but had let the problem percolate in my noggin until I came up with a solution that I completely understood and felt comfortable with.  The code makes extensive use of Lists and does customizable type-based sorting.  I tried a couple of different ways of doing the sorting, but settled on a BubbleSort due to the way it moved list items around to get the list sorted.  The BubbleSort allowed me to call the sorting subroutine for each sorted column, in reverse order.  So if the export is sorted by last name and then first name, I sort it by first name and then sort it by last name.

The next release for this project should come out very shortly.  I have already implemented OpenLog into new code, but want to get the web interface finished.  I will probably do the web interface in 2 stages, doing simple HTML that opens in Word/Excel and then implementing some of the Office XML schema.  I also need to make XML a format that is available.  Are there any other report formats that users would want?

OpenNTF My Projects page has a new look

Papa Bruce gave me the keys to the caddy and I decided to get the upholstery on the drivers side redone.  Since I have started to contribute more to OpenNTF, I wanted to have a way to look at of my projects at once.  The My Projects page already existed, but it didn't contain enough information.  So with Bruce's permission, I have updated it to make it more usable.  If you have contributed to an OpenNTF project, take a look at the new design and let me know what you think.  You can find a link to the My Projects page under the My Links menu on the far right after you login.

If there are any other changes you would like to see made to that page or any other part of the OpenNTF site, please let me know and I will run it by Bruce.

Using AJAX with Domino Webcast

If you work with Domino and are frighten, yet strangely excited, by the four letter word that is AJAX, you might want to sign up for the free IBM Webcast Using AJAX with Domino Web Applications.

AJAX (Asynchronous JavaScript and XML) can improve the performance and usability of your Domino Web applications. This Software Development Platform webcast will examine how you can modify your Domino Web applications to make use of AJAX.

Bring your enterprise Domino apps to the new Web 2.0 world.  Enough buzz words for ya?

Why modifying old code takes so long

Jeff Atwood has a great article talking about the difference in effort needed when modifying old code as opposed to creating a new application.  The opening graphic explained my current development life.  Even simple things that take just a couple of minutes to code take forever to actually get finished because I spend a large portion of time either figuring out or remembering exactly what the existing code does.

The other thing I agree with is that sometimes it's much more useful to see how an application is used as opposed to understanding how they technically did something.  When I look at a new application, I am more apt to ask what it does as opposed to how does it do it.  Only when I am completely stumped do I actually want to look under the covers and see how it's done.

Finally in English!
Philippe Gauvin has been running his blog, Web 2.0 & Domino, for quite a while, but it's written in French.  The code is always readible, but I often wonder how close the Google translator gets to the actual meaning of the original text.  Well, it seems that Philippe has decided to put out an English version of his blog.  The articles he puts out regarding integration of Domino and AJAX are first rate and I highly suggest you add him to your regular reading list.
Case for another field type in Notes
Ian Irving makes a case for adding a new field type to Notes.  In addition to Authors and Readers, he would like to have Deleters field type.  So instead of having to write lots of code within the QueryDocumentDelete to see if a person should be deleting a document, the Deleters field would do the work for you.  I think this is a great idea and would take a significant amount of complexity out of some applications.
Easy Tabs via JavaScript

Following my mantra of keeping things as simple as possible while still making them able to be reused, I spent the past couple of days thinking of a way to code a tabbed interface that I was tasked with building.  Making the tabs wasn't such a big deal using a modified sliding doors approach, but I didn't want to take my usual approach to the JavaScript for switching between tabs.  In the past, I have used a Switch-Case loop to go through the tabs and either show them or hide them.  The problem with that is that I have to know what all the tabs are going to be and it's a real PITA to add or remove tabs later on.

This time I thought I would try something a little more OO.  The function I wrote is only 25 lines long without much error checking.  The beauty of this function is that I can reuse this function as often as I want.  Here is the function:

function showTab(listname,tabname) {
 var obj = document.getElementById(listname);
 var obj2 = obj.getElementsByTagName('li');
 var obj3;
 var obj4;
 
 for (i=0;i < obj2.length;i++) {
  obj3 = document.getElementById(obj2[i].id.substring(0,obj2[i].id.length-2));
  obj4 = obj2[i].getElementsByTagName('a');
  if (obj2[i].id == (tabname + "li")) {
   if (obj3.style.display != "block") {
    obj2[i].className = "ActiveTab";
    obj3.style.display = "block";

    obj4[0].className = "ActiveTab";
   }
  } else {
   if (obj3.style.display != "none") {
    obj2[i].className = "InactiveTab";
    obj3.style.display = "none";
    obj4[0].className = "InactiveTab";
   }
  }
 } 
}

The listname variable is the name of the UL that contain the tab being clicked on.  The tabname is the name of the DIV that is is going to be displayed.  The only naming requirement is that the LI that is the tab for a given DIV be the same name as the DIV plus 'li' on the end.  For example, if the DIV is named 'personal', the LI will be named 'personalli'.  This code also takes advantage of the className attribute to change the look and feel of the tabs and only changes the tabs that is being activated or deactivated, which should help with performance.  Using className is so much easier than changing all of the CSS settings individually and also makes it really easy to change the UI of the page.

Beware the GetAllEntriesByKey bug

This is the second time in less than a year I have spent time killing myself over a bug inherent to LotusScript.  Here's the scenario:

  • I have a view with 3 categorized columns.  The first and third columns are single value fields on the form, while the second column is a multivalue field.  The view looks great when just viewing it.
  • Users click theough a number of screens to determine what documents to report on.
  • Based on the user's input, a 3 element array is created and passed to the GetAllElementsByKey method to get the list of documents to add to the report.
  • No matter what is passed in the key, no entries are returned.  If only a value that matches the first column is passed, the correct number of view entries.

This is a known bug with the GetAllEntriesByKey method, but it is something that will cause significant hair loss, none the less.  The really funny thing is that the method works fine if the first key is a multivalue column, but not on the second column. 

New OpenNTF Project - ASND Mass Mailer
I have released a new project on .  The ASND Mass Mailer is something that I had built for a previous employer to solve the problems with maintaining groups for communication purposes.  Instead of using groups to send out company wide communications, it uses information in the person documents to generate a list of recipients and sends out individual emails to each person without bogging down the router with having a huge BCC field.  Although there is nothing earth shattering about the code in this application, it's simplicity and usefulness should make it something you can easily deploy in your environment.
Things you learn with NotesPeek
I was using to troubleshoot a problem with profile documents this week and noticed something interesting.  It seems that the Domino Designer stores debugger break points as a profile document in the database you are working on.  I have used the Debugger often, but never thought about where the break points were stored.  The positive side of this is that no matter what replica you are working on, any break points you have previously set should be available.
Dojo Cheat Sheet
If you use the framework, this should be very helpful.  Plus, if your company has a plotter, it would look great on your wall.
AJAX DataGrid and Domino View - match made in heaven?

I have been reading a number of articles on the different AJAX frameworks available and have been trying to figure out how they can make normal applications better.  At the same time, I am busy converting a number of views from Domino generated HTML or Java over to custom DHTML.  While not very glamorous or extremely hard, the conversion does take quite a while to do and is labor intensive.  Wouldn't it be much better if Domino served up views in a JavaScript accessible datagrid like or based on .  By replacing the standard HTML views with AJAX based ones, IBM would be able to drastically update the UI of all Domino applications and really make strides to being able to play in the Web 2.0 space out of the box.  The biggest concern for me is how to handle categorized views.  Is it something that is handled with a combination of multiple widgets or something more like the way Excel does subtotaling?  I do wonder if this is one of the Aces up IBM's sleeve since they are now a full contributor to Dojo. Time will tell.

Tip: Specifying cell widths in Word
We do a lot of exports from Notes/Domino to and I have run into an issue lately that I thought I should share.  Most of the exports we do are in tabular format, which is pretty easy to do using HTML and Content-Type.  The gotcha I ran into recently is that some versions of Word (I am running 2000 Professional) only accept column widths in either inches or percent.  Since I most often specify widths in pixels, this caused a very nagging issue.  The report would look great as straight HTML, but would look completely different when exported to Word.  Switching the width to use percents fixed the problem completely and, since i am also setting the paper size and orientation, shouldn't have any negative effects.  Just one more thing to think about when interacting the MS Office.  I wonder if this has been fixed in later releases.
Looking for input

There are a couple of things that I want to do, but haven't figured out how to do it.  Both of these things revolve around incorporating into my Domino based blog.

  1. Daily Blog Posting from Del.icio.us - A number of blogs that I read have this feature enabled and I think it would greatly enhance my blog.  Of course, this is meant for Movable Type blogs, so an agent will have to be written to handle it.  In short, it posts an entry to your blog with all of the blog entries that you have bookmarked in the previous 24 hours.  It will certainly influence my use of del.icio.us.
  2. Performancing - Firefox extension that allows you to write blog entries while browsing the Internet.  It supports a number of APIs based on XML-RPC.

The biggest hurdle I am running into is handling the authentication with the Agent/Domino Server.  None of the examples I have seen deal with a system that is secure and most only deal with writing an agent that connects to another server via XML-RPC.  Once I have this licked, everything else, even the fact that I have to used Java to write the agent, should be a snap to accomplish.

If anyone has any experience with the XML-RPC specification, any help would be greatly appreciate.

10 easy steps to using Regular Expressions

Russ Olsen has published a two part article on learning .

Regular expressions are simply strings that we use to match other strings. If you give me a regular expression, perhaps ‘Ols[eo]n‘ and a string, perhaps ‘Olsen‘ or ‘Olson‘, I can tell you if the strings match the regular expression (they both do). Most computer users deal with this kind of pattern matching all the time — Windows users frequently look for Word documents by specifying ‘*.doc‘. While the ‘*.doc‘ type patterns are great for what they do, when you are doing complex pattern matching, you need a serious tool. Regular expressions are that tool.

I have never been a fan of regular expressions because I have always had a hard time reading them.  These articles go along way to making things much easier for me.  It will also be able to make my Javascript significantly more powerful.

Damien Katz is uber smart
It's not like you need to hear this from me.  Listen to the latest Taking Notes podcast with him and you too can feel as inferior as I do.
Take the right AJAX Vitamin

Shaun Inman's article on Responsible Asynchronous Scripting goes to the heart of the problem with some of the new applications, even if he doesn't spell his name correctly.

On the other hand, avoid replacing traditional navigation with XHR. Doing so breaks an inherent functionality responsible for the success of the web: Universal Resource Locators. Unless we reinvent the wheel and restore this functionality ourselves (hidden iframes and fragment identifiers anyone?), it is impossible to link directly to content included with XHR.

He goes on to point how a number of things that developers should keep in mind when deploying on a site, not the least of which is keeping the user informed that something is happening.

This article is a prime example of the good work being published on Vitamin.

June DCNUG Meeting this week!

The June 8th DC Lotus User Group Meeting
Thursday Night, 6:30 to 8:00

Lots of free stuff: CDs, Magazines, and T-Shirts. Be sure to register by sending Jack@LeadershipByNumbers.com an e-mail.

AGENDA:

  • Kevin Petitt on (it's been improved). If you create agents, you need this information. If you administer applications with agents, you need this information. If you use Lotus programs with agents, you need . . . well, you know. Kevin has worked closely with OpenLog, and is a contributor for OpenNTF.org
  • . Not only can it work, you can benefit from greater security and lower costs. Desktop Linux continues to be a very hot topic. Ubuntu Linux has released the first enterprise-grade free version of Linux, and is getting rave reviews. Imagine, it's as good (or better) than RedHat or SuSE. It's incredible.
    • Why consider desktop ?
    • How to install Notes on Linux (ND6 and R7)

FREE CDs GIVEN TO EVERYONE WHO CONFIRMS ATTENDENCE BY SENDING AN EMAIL TO JACK@LEADERSHIPBYNUMBERS.COM

  • UBUNTU LIVE LINUX CD (works w/o installing Linux on Hard drive)
  • VMWare Server for Linux (beta, which will allow Windows to run on Linux)
  • Latest Lotus Notes Advisor Magazine (very cool articles)
  • Notes Advisor T-Shirts
Buttonator uses AJAX for good
Part of the new  world, Buttonator.com allows you to easily create attractive buttons via drag and drop.  Even if you never use it to create a button, it will definitely give you some ideas for using .
Some recent CodingHorror gems

CodingHorror.comIf you do not subscribe to Jeff Atwood's CodingHorror, do yourself a favor and start reading it.  Here are just a few of the articles that he has written lately.

DCNUG Meeting last week - IBM Workplace Forms

Last week's DCNUG meeting was pretty good.  IBM brought the dog and pony show into town and displayed their latest addition to the Lotus family of products.  This was my first real look at the IBM Workplace Forms.  Prior to the meeting, I didn't quite understand what the uproar around XForms was all about, but I think I get it now.  For me, I see XForms as what Notes and Domino might have grown up to be one day.  The beauty of the product is that the design of the form and the data users enter into it is contained within a relatively small XML file.    Although you might need the Viewer to see the "pixel perfect" form, the data in the file is nothing more than ASCII text, so it will most certainly be readable by anyone in the future.  I am not sure that Adobe's Acrobat eForms or Microsoft's Infopath will be able to say the same thing.  I can almost envision a day in the future where this product, or a simpler version, is a part of Domino out of the box.

Along with the paper like forms that it can create, the designer can also create front end wizards to give users a friendlier UI for entering data onto the form.  The configurability of the form and the elements on it is very robust.  The choices were almost dizzying as they were demonstrated, but no more than the IBM R.A.D. tool.

So, big deal.  It's another XML file format with a server and design tool.  Well,  in my mind, this XML file is akin to a Notes document that has a stored form attached, but it's contained within a single file.  The file is encrypted and able to be digitally signed (and resigned without significantly increasing the file size: See Adobe!) and can easily be transported to any other system.  Any workflow or scripting is contained within the file itself, so it does not require a back-end server farm to process it.  The file can then be entered into your favorite document management system or the data can be extracted and transferred to a back-end RDBMS system before being archived like any other business record.  The documents are able to be viewed or edited using either the Viewer software loaded on a local machine or via a browser when being served up by the Workplace Forms Server without installing a plug-in.  One of the coolest features is that a user can save an incomplete form locally to be finished later, even anonymous users.  No more setting cookies or saving documents in your database in case the user might come back.  And this

From what we heard, the next version, 2.6, will be compliant with the recently ratified XForms 1.0 specifications.  They are also working on separating the design of the form from the underlying data and workflow so they can better serve a variety of client platforms.  This sounds strikingly familiar to the way Notes and Domino works.  In my mind, I have always seen Notes documents as a genetically modified XML document.  It contains all the data while the form contains the schema necessary for displaying it to the user.  I am not sure of the costs of an implementation of the product, but I hear it's not cheap.

Hopefully, I will be able to play with this technology sometime in the near future.  Mauri ce, if you're out there, I am still waiting on that email!

 

For those old folks trying to develop on the web
I was working on some CSS positioning today and was having quite an issue trying to get everything to line up correctly.  Then it came to me in a flash of desperation that Windows has a tool that can help me without loading anything at all.  As a part of any normal Windows installation, a little program called Magnifier gets installed.  As well as being able to help those of us who are quickly on the way to trifocals to read 14pt font, it can also be used to get a better look at things on a web page at the pixel level.  It helps quite a bit when trying to line up things, especially when using some of the add-on tools like the IE DevToolBar and the Web Developer Toolbar for Firefox.  It's much easier to see how far off outlined elements are and more accurately use the ruler tools.
A couple of CodeBetter entries

Sometimes it is easy to forget that the code we write can have significant impact on other people.  Jay Kimble's What We Do Matters entry reminds us that it can:

I know you say “Jay you used to work on a corporate reporting system.  How could that hurt people?”  Easy, my system was used to make layoff decisions.  There were numbers in my app that I knew were there to help decide when people should be laid off.  If one of my numbers was slightly off it could have caused a bunch of layoffs.  If my numbers were too high it could bankrupt the company.

Just remember this the next time you are hurriedly throwing together a report for some big-wig right before a big meeting.

Jeffrey Palermo wonders if writing software is too easy these days.

Tools today make custom software too easy to develop.  This statement may seem controversial, but I believe it.  I’ve seen too many unskilled people take a programming tool and throw together something that seems to work initially.  The business folks can’t tell a fraud from an expert or evaluate the work, so they use it and then feel the pain later when they actually have to maintain it.

I know that many of us in the Domino world have seen our share of really bad applications.  In fact, that was the topic of our last DCNUG meeting.  Jeff's points are well laid out and I agree with most of what he says.  I do think that software development is part engineering, part artistry.  The really good developers (Masters) will build something that blows you away and then show you how it's done, and you will be able to understand it and use the same techniques in your own code.

Even if you build it, they can’t come if they can’t find it

It's Friday at 5 PM and you've just finished adding the latest great feature to your pet project.  You've spent more than a week developing it to exceed anything your users would have expected.  With the enthusiasm of a proud parent, you send a note out indicating that the new feature has been tested and in now available in production.  You're walking on air as you head out the door and home to a much deserved cold cervace.  Fast forward a couple of weeks when you run into one of the users in the hall and ask them how they like that kewl new functionality.  They sheepishly indicate that they haven't used it yet.  After picking your jaw and pride off the floor, you chalk it up to a PEBCAK problem that the user had and decide to ask someone else about it.  Imagine your surprise when the second, third, and fourth users haven't used it either and are still doing things the old way.  After some evasive answers, they admit that they just couldn't figure out where the feature was nor how to use it.

Although not to this extreme, I am sure that every developer out there has had this happen to them.  Nathan wrote recently about Lotus needing to completely rewrite the UI for Hannover because the current client has become too cumbersome for users.  Jeff Atwood has written about following a simple rule when developing:  If the user can't find it, the function's not there.  Simplicity and obviousness is not something that is the same for all people.

I still have this button clipped to my mug boss to periodically remind me that, no matter how cool the feature may be, if users can't find it-- or understand it-- you're wasting your time. So make sure you have your priorities in order before you start: usability first, feature second.

When developing anything, it is important for the learning curve of the application to be as gradual as possible.  The applications I always find most daunting as a user are the graphics apps like PhotoShop and Paint Shop Pro.  If you are a noobie to photo editing, just opening these applications is enough to make you shiver in fear.  You definitely need to go through the tutorials to be able to do most of what you want and, unless you use the program all the time, you will need go through them often.  This is what I try not to do when I am developing my own applications.

Everyone needs to CodeBetter.com
I stumbled upon CodeBetter.com recently and have found it to be a great source of information on writing good code.  Recent articles include guidelines for writing methods, a beginning tutorial for XSLT, how to redesign old code, and when to run.  If you don't already have it on your feed list, adding it will only make your programming abilities grow.
Know LotusScript and want to learn about Java?

After stiring up a nest of hornets by asking if a lack of Java skills is hurting the Notes Community, Mikkel Heisterberg has written a number of entries this week on getting us slackers in line with his Java in Notes/Domino Explained series:

Mikkle's knowledge of Java is explained in such a way that we LotusScript kiddies can easily understand it.  Hopefully, he will continue to share more with us in the coming weeks.  I know that I will continue to read it with great interest.

Issue with Firebug and AJAX

I have recently run into a problem with Firebug and XMLHTTP requests (AJAX).  It seems that the current version of the extension has issues with synchronous calls rather than asynchronous calls.  The difference between the 2 types is that is the same as the modality of Notes dialog boxes.  Like modal boxes, synchronous calls cause the browser to wait until the request is completed.  Asynchronous calls, on the other hand, make the call and continue on, not waiting for the response to come.  The difference is important if you are chaining multiple AJAX calls together and a later call is dependent on a response from previous one.  I will have to keep this in mind when I add the report functionality I discussed earlier.

Using an agent to work around the AJAX cross domain limitation

AJAX is a great tool for building some really slick applications, but it has one limitation has made me ignore it for a number of things that I have wanted to do. After speaking with Chris Toohey and stealing getting inspiration from some code that he had, I have come up with a simple agent to allow me to get data from other domains. The agent is incredibly simple, but I have no idea what kind of impact this will have on the server, so test thoroughly before you implement it in your environment.

First, create an new LS agent and set the run-time security level to 2. Allow Restricted Operations. The agent can be as complex as you want it to be, but below is all that is necessary:

Dim s As New NotesSession Dim doc as NotesDocument Dim xml As Variant Dim temp As String Set doc = session.DocumentContext temp = StrRight(doc.Query_String_Decoded(0),"url=") Set xml = CreateObject("Microsoft.XMLHTTP") xml.Open "GET", temp , False xml.Send Print "Content-Type:text/html" Print "Cache-Control:NoCache" Print Cstr(xml.ResponseText) Set xml = nothing
This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

Finally, you create an AJAX call on your page that calls the agent you created and pass the URL you want to retrieve as a query string parameter. That's all it takes to basically have one AJAX call access another AJAX call to get data from some other domain. Just to prove that my dogfood tastes good, I am using a version of this agent to retrieve my Bloglines Blog Roll on the right. Just click on the RSS icon to see the data retrieved via AJAX.

SnT Thursday: Elapsed Time Class

As I has said earlier in the week, I have been doing a lot with LotusScript classes for reports I have been generating. One of the new @Functions that Damien introduced in the R6 formula engine was @BusinessDays. What started out as trying to find a simple way to determine the number of days between 2 dates turned into the custom class you see below:

Public Class elapsedtime Private startdt As NotesDateTime Private enddt As NotesDateTime Private tstring As String Private bstring As String Private tdays As Long Private bdays As Long Private thours As Integer Private bhours As Integer Private tminutes As Integer Private bminutes As Integer Private tseconds As Integer Private bseconds As Integer Private nondays () As Integer Private nondates () As NotesDateTime Private startofwd As String Private endofwd As String Sub New(dt1 As String, dt2 As String, holidays As String) Dim tmp As Variant Dim x As Integer Set Me.startdt = New NotesDateTime(dt1) Set Me.enddt = New NotesDateTime(dt2) If dt1 = "" Or dt2 = "" Then Exit Sub Redim nondays(1) nondays(0) = 1 nondays(1) = 7 Me.startofwd = "8:00 AM" Me.endofwd = "6:00 PM" If holidays <> "" then tmp = Split(holidays,",") Redim nondates(Ubound(tmp)) For x = 0 To Ubound(tmp) Set nondates(x) = New NotesDateTime(tmp(x)) Next End If Call GetTElapsed() Call GetBElapsed() End Sub Private Sub GetTElapsed() On Error Goto gteerror Dim diff As Long, tmp As Long If Me.startdt.localtime = Me.enddt.localtime Then Me.tdays = 0 Me.thours = 0 Me.tminutes = 0 Me.tseconds = 0 Else diff = Me.enddt.TimeDifference(Me.startdt)/60 Me.tdays = Fix(diff/1440) Me.thours = Fix((diff Mod 1440)/60) Me.tminutes = Fix(diff Mod 60) Me.tseconds = Fix(Me.enddt.TimeDifference(Me.startdt) Mod 60) End If gtefinish : Exit Sub gteerror : Call LogError() Resume gtefinish End Sub Private Sub GetBElapsed() On Error Goto bteerror Dim dt1 As NotesDateTime Dim dt2 As NotesDateTime Dim dt3 As NotesDateTime Dim hdt As NotesDateTime Dim diff As Long, tmp As Long Dim stepcount As Integer Dim addit As Boolean Me.bdays = 0 If Me.startdt.localtime = Me.enddt.localtime Then Me.bhours = 0 Me.bminutes = 0 Me.bseconds = 0 Exit Sub Elseif Me.enddt.TimeDifference(Me.startdt) < 0 Then stepcount = -1 ' Based on the time of the startdt, the values are recalculated to fall within work hours If Hour(Me.enddt.TimeOnly) >= 18 Then Set dt1 = New NotesDateTime(Me.enddt.DateOnly & " " & startofwd) Call dt1.AdjustDay(stepcount) Elseif Hour(Me.enddt.TimeOnly) < 8 Then Set dt1 = New NotesDateTime(Me.enddt.DateOnly & " " & startofwd) Else Set dt1 = New NotesDateTime(Me.enddt.LocalTime) End If ' if the start date falls on a weekend or holiday, move it While CheckIfWorkDay(dt1) = False Call dt1.AdjustDay(1) Wend ' Based on the time of the enddt, the values are recalculated to fall within work hours If Hour(Me.startdt.TimeOnly) < 8 Then Set dt2 = New NotesDateTime(Me.startdt.DateOnly & " " & endofwd) Call dt1.AdjustDay(-1) Elseif Hour(Me.startdt.TimeOnly) >= 18 Then Set dt2 = New NotesDateTime(Me.startdt.DateOnly & " " & endofwd) Else Set dt2 = New NotesDateTime(Me.startdt.LocalTime) End If ' if the end date falls on a weekend or holiday, move it While CheckIfWorkDay(dt2) = False Call dt2.AdjustDay(-1) Wend Else stepcount = 1 ' Based on the time of the startdt, the values are recalculated to fall within work hours If Hour(Me.startdt.TimeOnly) >= 18 Then Set dt1 = New NotesDateTime(Me.startdt.DateOnly & " " & startofwd) Call dt1.AdjustDay(stepcount) Elseif Hour(Me.startdt.TimeOnly) < 8 Then Set dt1 = New NotesDateTime(Me.startdt.DateOnly & " " & startofwd) Else Set dt1 = New NotesDateTime(Me.startdt.LocalTime) End If ' if the start date falls on a weekend or holiday, move it While CheckIfWorkDay(dt1) = False Call dt1.AdjustDay(1) Wend ' Based on the time of the enddt, the values are recalculated to fall within work hours If Hour(Me.enddt.TimeOnly) < 8 Then Set dt2 = New NotesDateTime(Me.enddt.DateOnly & " " & endofwd) Call dt2.AdjustDay(-1) Elseif Hour(Me.enddt.TimeOnly) >= 18 Then Set dt2 = New NotesDateTime(Me.enddt.DateOnly & " " & endofwd) Else Set dt2 = New NotesDateTime(Me.enddt.LocalTime) End If ' if the end date falls on a weekend or holiday, move it While CheckIfWorkDay(dt2) = False Call dt2.AdjustDay(-1) Wend End If While dt2.dateonly <> dt1.DateOnly And dt2.TimeDifference(dt1) > 0 If CheckIfWorkDay(dt1) = False Then Me.bdays = Me.bdays + 1 End If Call dt1.AdjustDay(1) Wend If Hour(dt1.TimeOnly) > Hour(dt2.TimeOnly) Then 'If the tim of the start is later in the day then the time of the end, go back to yesterday 'and calculate the time differently and subtract a business day 'diff = (seconds from start to end of work day) + (seconds from start of workday to end) Call dt1.AdjustDay(-1) Me.bdays = Me.bdays - 1 Set dt3 = New NotesDateTime(dt1.DateOnly & " " & endofwd) diff = dt3.TimeDifference(dt1)/60 Set dt3 = New NotesDateTime(dt2.DateOnly & " " & startofwd) diff = diff + (dt2.TimeDifference(dt3)/60) Else diff = dt2.TimeDifference(dt1)/60 End If Me.bdays = Me.bdays * stepcount Me.bhours = Fix(diff/60) Me.bminutes = Fix(diff Mod 60) Me.bseconds = Fix((dt2.TimeDifference(dt1)) Mod 60) btefinish : Exit Sub bteerror : Call LogError() Resume btefinish End Sub Private Function CheckIfWorkDay(dt) As Boolean Dim i As Integer CheckIfWorkDay = True Forall x In Me.nondays If Weekday(dt.DateOnly) = x Then CheckIfWorkDay = False Exit Forall End If End Forall If CheckIfWorkDay = True Then For i = 0 To Ubound(nondates) If dt.DateOnly = Me.nondates(i).DateOnly Then CheckIfWorkDay = False i = Ubound(Me.nondates) End If Next End If End Function Public Function GetTimeString(elapsetype As String) As String On Error Goto gtserror Dim thisdays As Long, thishours As Integer, thisminutes As Integer, thisseconds As Integer Dim tmp As Integer If elapsetype = "B" Then thisdays = Me.bdays thishours = Me.bhours thisminutes = Me.bminutes thisseconds = Me.bseconds Else thisdays = Me.tdays thishours = Me.thours thisminutes = Me.tminutes thisseconds = Me.tseconds End If tmp = 0 If thisdays = 1 Then GetTimeString = "1 Day" Elseif thisdays = -1 Then GetTimeString = "-1 Day" Elseif thisdays <> 0 Then GetTimeString = Cstr(thisdays) & " Days" End If If GetTimeString = "" Then tmp = thishours Else tmp = Abs(thishours) If tmp = 1 Then GetTimeString = GetTimeString & " 1 Hour" Elseif tmp = -1 Then GetTimeString = GetTimeString & " -1 Hour" Elseif tmp <> 0 Then GetTimeString = GetTimeString & " " & Cstr(tmp) & " Hours" End If If GetTimeString = "" Then tmp = thisminutes Else tmp = Abs(thisminutes) If tmp = 1 Then GetTimeString = GetTimeString & " 1 Minute" Elseif tmp = -1 Then GetTimeString = GetTimeString & " -1 Minute" Elseif tmp <> 0 Then GetTimeString = GetTimeString & " " & Cstr(tmp) & " Minutes" End If GetTimeString = Ltrim(GetTimeString) gtsfinish : Exit Function gtserror : Call LogError() Resume gtsfinish End Function Public Function GetDays(thistype As String) As Long If thistype = "B" Then GetDays = Me.bdays Else GetDays = Me.tdays End If End Function Public Function GetHours(thistype As String) As Integer If thistype = "B" Then GetHours = Me.bhours Else GetHours = Me.thours End If End Function Public Function GetMinutes(thistype As String) As Integer If thistype = "B" Then GetMinutes = Me.bminutes Else GetMinutes = Me.tminutes End If End Function Public Function GetSeconds(thistype As String) As Integer If thistype = "B" Then GetSeconds = Me.bseconds Else GetSeconds = Me.tseconds End If End Function End Class
This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

If you notice, none of the attributes of the class are exposed as Public variables. I did this on purpose because I wanted to make sure that they weren't monkeyed with by any external code. The New() subroutine is passed the start and end date times as strings as well as a comma separated string of holidays. The decision to make the holidays a variable that is passed in will allow this to be used internationally, where the holidays for one division of the company might be different than the others. The weekend days are automatically set to Saturday and Sunday, but they can be changed if your company works something other than a standard 5 day work week. All of the other public functions get passed a variable, B for Business and T for Total, and return the values of the private attributes. The most useful finction is the GetTimeString function, which returns the elapsed time in days, hours, and minutes. This is just the type of thing that managers love to see on their reports.

Falling in lust with Classes

I have used custom Classes within LotusScript some in the past, but am really starting to fall in lust with them as of late.  Last August, I wrote a series of articles on using Lists to write decent reports in Notes.  Well, this past week, I took things a bit further by creating my own classes to replace the Lists I had used before.  Well, that's a bit misleading, because my classes were really objects that contained lists of other classes, that were lists of objects.  Capice?  Of course, I really blame all of this on Jerry and his damn SnTT Tip.

While I don't think that the change to using classes for this agent saved me any coding over my procedural code I had before, it did allow me to control the data integrity better and hopefully improve the performance of the application.  One thing I did notice while developing my classes was that I started to change the way I viewed the data and actions I wanted to perform.  Everything started to look like objects and I started to see the actions I wanted to perform as subs or functions for those objects.  I am not sure if it made my code any clearer, but it certainly made my mind see what I had to do much easier.  It also allowed me to be able to expand this report in the future without having to change any of the underlying data structure of the classes.

The easiest way to think of using classes is to see everything in Notes as a list.  A database is just a list of notes, both data notes and design notes.  A view is just a list of documents.  A document is just a list of fields.  A field is just a list of values.  And so forth.  Since everything is just a list, creating classes that handle those lists should be relatively easy.  Just as a NotesDocumentCollection is a list of NotesDocuments, you could create a class called CatalogItems that could contain a list of attributes for a given item.  Then you could create a class called Catalog that could contain a list of CatalogItems.  Once you start, the variations and options seem almost endless, just like the first time you were introduced to Notes.

The most important new keyword you need to learn when creating custom classes is Me.  Me is used to reference attributes of the class that you are working in using dot annotation.  Just as you would use doc.UniversalID to get the document id for a Notes document, you can use Me.attname to get or set the value of the attname attribute.  If an attribute is Public, it can be read and set directly by other code using dot annotation.  If it's Private, it can only be read or set by functions and subs within this class.  Basically, if you want to be able to control access to the attributes of a class, set them to Private and use Public functions within the class to get or set the variables.  If this seems Greek to you, don't worry, it did to me too.  If you have any questions, feel free to respond here or ask more knowledgeable people like Wild Bill.  H is knowledge of OO programming makes me proud to be a Scot.

So basically I created a class that was my entire results table.  That class had a number of attributes, the most important of which was the one that was a List of rows.  The rows were another class that had contained the values for each column as attributes.  And since the values of the classes are loaded into memory, any manipulation or retrieval of the data is significantly faster.

Later on this week (think Thursday) I will release a class I wrote to calculate elapsed time, both total and business.  A simple example such as that should help if you are still having trouble visualizing what I am talking about.

When Transparent isn’t so see through

Being the good little CSS developer that I am, I have been trying to make sure that I set the background color of all elements for which I set a foreground color.  This is something that the W3C checks when it validates your CSS.  This is all well and good most of the time, but it caused me quite a headache this morning.

I was trying to use one of the SnT Thursday tips that Esther posted for hi-lighting rows on a table.  I wanted to use this in a couple of reports I was creating via LS and was running into unforeseen issues.  After pulling my head out of my butt and using the right JavaScript capitalization (it's always the little things that get you), I was unable to get it to work correctly in IE.  Viewing it through FireFox showed me that was my code was correct, but the change wasn't being honored in IE.  What's worse was that I could actually see that the change was occurring while watching the DOM using the IE Developer Toolbar.

The culprit causing the problem was the fact that I had set the background-color to transparent for all TD in the table.  FireFox took this to mean that if the color of the row changed, the color of the cell must change also.  IE decided that the cell color was what it started out with no matter what happened on the row below.  Once I removed the background-color setting, the color changes worked as expected in both browsers.  I hope this quirk is something that is fixed in IE7.

A Re-introduction to Javascript
I know that all of my readers are expperts at Javascript.  But even those as knowledgeable as yourselves can use a refresher every now and then, if for no othe reason than to make you feel good about how much useless information you have crammed in your noggin.  Besides, we all tend to fall into a rut when we develop and find ourselves using the same functions over and over without thinking if there might be a better way.  A (Re)-Introduction to JavaScript by Simon Willison is just such a site.  Even if you are a seasoned web developer, you might find a nugget or two of information in the many paragraphs of straight forward information.
©Function Equivalents with Lists

As a Notes Developer grows, we tend to move from @Functions to LotusScript to JavaScript. Since Notes is so great a handling lists, we get very attached to our @Implode and @Explode functions. So attached, that I have seen many a Evaluate({@Implode(list,",")},doc) in LotusScript routines. But fear not, young coders, there are LotusScript functions that can perform the same tasks for you.

First, I have to let the n00bies off the hook a bit. When you look at the Help document for @Implode or @Explode, it doesn't list a LotusScript function that is their equivalent. That would lead n00bies to believe that such a function doesn't exists and they either need to write their own function (Guilty!!!) or use Evaluate. Below are a few of the functions I use the most:

@FunctionLotusScriptJavascript
@ImplodeJoin Join
@ExplodeSplitSplit
@WordStrTokenWrite your own

Use of these new native functions should greatly increase the performance of your code.

Why Features Don’t Matter Anymore

Damien pointed me to this very important article for all developers.

1) More features isn't better, it's worse.

Feature overload is becoming a real issue. The last thing a customer wants is confusion-and what's more confusing than comparing technical specifications, unless you are en expert? Only nerds get a kick out of reading feature lists. (I know - I'm one of them.)

I can't tell you how many small programs I use that do just one thing well.  And I can count on my fingers the number of features in Word and Excel I use on a regular basis.  The KISS methodology has been sorely lost on the computer industry.  I will try to keep the points in this article in mind whenever I find myself adding something cool to an application.

Links and MimeEntities

I am running into a problem and am wondering if anyone out there has done something similar that I might be able to steal learn from.  The issue is being caused by Outlook's/Exchange's handling of long URLs.  My standard notification library has the ability to add a URL link to the document at the bottom of the notification.  Since these are Domino URLs, they tend to be very long.  When I open up the links in Outlook, they are wrapped after a certain number of characters and the link no longer works.

I know that you can use the MimeEntity class to spit out HTML for email messages, so I looked into using it for this problem.  Unfortunately, all the examples I found deal with creating the email completely from scratch.  My workflow solution allows database managers to write Rich Text notification templates that are used to pull data from the document that is being processed.  This works very well and it is not something that I am going to be able to easily give up.  In a nutshell, here is what I want to happen:

  1. Get a handle on the notification template document.
  2. Create a new document based on the template.
  3. Replace all of the values on the template with values from the document being processed.
  4. Depending on a variable on the template form, append either HTML, a URL, or doclink to the notification.
  5. Send notification.

The HTML I want to append in #4 is a simple Click Here to access the document. Nothing special, right?  Well, the problem comes when I try to access the RichTextField on the document and treat it like a MimeEntity.  If I try to create a new MimeEntity with the same name, it gives me an error like I would expect, but it won't let me access the existing field and create a child entity to it.  Am I going down the wrong path or is there something simple I am missing? 

Here is some of code in question:

If mail.AppendLink(0) = "URL" Then
  Call body.AppendText(urltext)   
 Elseif mail.AppendLink(0) = "Doclink" Then
  Call body.AppendText("Click here to access the document -->")
  Call body.AppendDocLink(doc,"Link to document")
 Elseif mail.AppendLink(0) = "URL Link" Then
  session.ConvertMime = False
  Set stream = session.CreateStream
  Set mime = mail.CreateMIMEEntity
  Call stream.WriteText(|

Click here to access the document|)
  Call mime.SetContentFromText(stream,"text/html",ENC_NONE)
  Call mail.CloseMIMEEntities(True)
 End If

Any help at all would be appreciated.

Must have Firefox externsion for AJAX

If you are starting to develop AJAX enabled pages, Firebug is a must have extension for Firefox.

FireBug is a new tool that aids with debugging Javascript, DHTML, and Ajax. It is like a combination of the Javascript Console, DOM Inspector, and a command line Javascript interpreter.
I have just started playing with it, but have already found it to be very useful in debugging problems.

via Jack Dausman

The real reason developers drink so much
Dilbert Cartoon
Whose Fault Is It When Collaboration Software Sucks?
Michael Sampson asked the above question and then did a wonderful job of answering it. In the end, it seems that developers and users are responsible for the success of a platform.
People offering services in this area have to be well trained and experienced ... because the product is so flexible, and there are different ways of doing things, you can make a great job or a right royal stuff-up. But that's no different than with any other software development environment; if a developer gets the constructs and underlying logic wrong in a C++ project, it won't work as to specification and the users will be most unhappy. Due to the rapid application development environment, Notes/Domino do have some redeeming factors over say C++, but nonetheless, if the underlying data model is wrong from the word go, it is expensive and time consuming to find redemption.
This statement couldn't be more accurate nor more frustrating. If it's so easy to screw up other platforms and even harder to fix them when they are screwed up, then why does Notes/Domino get such a bad rap? Is it due to it's ability to adapt easily and therefore it should have been done right in the first place? Or is it because, unlike with a lot of other languages, developers of Notes/Domino applications have to be part business analyst, part systems analyst, part developer, part designer, and part trainer. I can't say as I have ever met a VB or C++ developer that does all the requirements gathering, developing, and training for a given system. They may actually do this sort of thing, but not at any of the companies that I have worked at.

In the end, it is important that you understand how essential your roles as a Notes Developer is to the success or failure of the platform at your company. While at times it might seem unfair to ask so much of just one person, think of the opportunities you have to shape the way your company does business for years to come.

via Shared Spaces Research & Consulting: The Week in Collaboration, Jan 23-27 2006

I HATE Caching
I hate caching. I can't put it any other way. I have been battling a caching issue in IE for that last couple of days and have finally clubbed it to death, but not without having a few more grey hairs. (No snide hair comments are allowed, children!!!)

Any ways, IE has what they like to call an "intelligent caching system". That's an overstatement on the level of "you're much prettier than Adriana Lima", especially when it comes to the way it handles Domino. By default, IE's cache setting (Temporary Internet Files) is set to Automatic. This allows IE to decide when it needs to bring down a new copy of a page and when it can simply use the copy in the cache. This is all well and good when you are looking at static HTML pages, but blows monster chunks when you are trying to use a Domino application. As a Domino developer, the solution to this problem is relatively simple, but there are many pieces.

The easiest way to control caching is by creating a Web Site Rule. This article from LDD does a great job of walking you through how to create a rule. One thing to remember if you are using Quickplace is that, since Quickplace doesn't support multiple Internet Site documents yet, you will need to create a Global Web Settings document and then create the rule from there.

You can also do it on a form by form, page by page basis using either @SetHTTPHeader or META tags in the HTMLHead. Most of the time, this is very easy to do and can allow you to granularly control which pages and forms are cachable. Check your Administration Help database for more information on how to use @SetHTTPHeader and its counter-part @GetHTTPHeader.

Neither of these ways will work for Quickplace, however. To control caching of Quickplace data, you need to modify the qpconfig.xml file and either modify or add the following lines:

<browser_caches_place_content enabled="false"/>
<page_compression enabled="false"/>

This should help tremendously, but is no guarantee that user's won't have caching issues.

Cache control is even more important when it comes to AJAX. I have been working a bunch with Domino Agents returning data via XMLHTTP calls. While I knew that the first Print statement had to be "Content-type: text/xml" (although "Content-type: text/plain" also seems to work), I was unaware of the issues with caching. To prevent your AJAX calls from being cached, the second Print statement has to be "Cache-Control: no-cache". Otherwise, agents that are called using the same URL will not be access on the server and will instead be access in the cache, regardless of the data that is actually returned. This can be very important if the data on the back end changes without the URL changing.

Following the tips above should allow your site to work correctly, regardless of how your user's browser settings are configured.

QUICKPLACE: How to figure out which local groups a user is a member of using JavaScript and AJAX
I have been working on a Quickplace 7 application recently that required me to determine which local groups user is a member of. In a regular Notes database, this wouldn't even be an issue since I would be able to use Hide-When formulas with @Functions and LotusScript. In a Quickplace, I am limited to using a ton of JavaScript and possibly calls to external agents via AJAX. After talking with the QP guru, Rob Novak, I was able to come up with a solution that is very quick and reusable.

First, I added the following JavaScript to the HTML file for the Page and List Folder Layouts:

var xmlhttp=false;
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
@end @*/

if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
  xmlhttp = new XMLHttpRequest();
}

var dbloc = getHomeFolder('34');
  
xmlhttp.open("GET", "/PlaceCatalog.nsf/QPLocalGroup?OpenAgent&qp=" + escape(dbloc) + "&user=" + escape(haiku.loginName));
xmlhttp.onreadystatechange = function() {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    try {
      ' Display everything that is hidden by default
    }
    catch (err) {
      document.getElementById('column_left').style.display = '';
    }
  }
}
xmlhttp.send(null);

The getHomeFolder function returns the directory for the current Quickplace. The code for QPLocalGroup is very simple agent that I placed in the Place Catalog database.

Sub Initialize
  Dim session As New NotesSession
  Dim db As NotesDatabase
  Dim view As NotesView
  Dim collection As NotesDocumentCollection
  Dim agentdoc As NotesDocument
  Dim doc As NotesDocument
  Dim parameters List As String
  Dim nname As NotesName
  Dim outbuf As String
  
  Set agentdoc = session.DocumentContext
  Print "Content-type: text/plain"
  
  Call PutQSIntoList(parameters,agentdoc.Query_String_Decoded(0))
  
  Set db = New NotesDatabase("",parameters("qp") & "Contacts1.nsf")
  Set view = db.GetView("h_PeopleByGroup")
  Set nname = New NotesName(parameters("user"))
  Set collection = view.GetAllDocumentsByKey(nname.Canonical,True)
  Set doc = collection.GetFirstDocument
  outbuf = "None"
  
  While Not(doc Is Nothing)
    If outbuf = "None" Then
      outbuf = doc.GetItemValue("h_Name")(0)
    Else
      outbuf = outbuf & "," & doc.GetItemValue("h_Name")(0)
    End If
    
    Set doc = collection.GetNextDocument(doc)
  Wend
  
  Print outbuf
End Sub

The agent returns the list of groups separated by commas, which is easy enough to step through using JavaScript. The PutQSIntoList function puts the parameters of the QueryString into a List object for easy access. The displayContent function can be placed in either the layout HTML pages or in individual imported pages, but not both. Here is an example of what the function might look like:

function displayContent(groups) {
  var tmp = new Array();
  
  tmp = groups.split(",");
  for (var i=1;i     switch (tmp[i-1]) {
      // snipped
    }
  }
}

I had searched for a similar solution on the LDD Forums, but found nothing, so I hope this might help someone else out there. As far as speed is concerned, there is no discernible delay due to the AJAX call.

Simple LS Function to create Connection Documents
The LotusScript function below can be used to setup Server Connection documents for Notes Clients. It checks to see if a connection document for a server exists before trying to create a new one. Nothing too special, but it beats writing instructions on how to do it by hand.

Sub ConnectionDocument(formalname As String,connectiontype As String, tcpip As String,location as string)
  Dim collection As NotesDocumentCollection
  Dim doc As NotesDocument
  Dim servername As NotesName
  
  Set servername = New NotesName(formalname)
  
  Set collection = localdb.Search(|Type = "Connection" & @LowerCase(@Name([CN];Destination)) = @LowerCase("| & connectionname & |")|,Nothing,0)
  If collection.count = 0 Then
    Set doc = localdb.CreateDocument()
    doc.Form = "Connection"
    doc.Type = "Connection"
  Else
    Set doc = collection.GetFirstDocument
  End If
  
  Select Case connectiontype
  Case Is = "LAN" :
    doc.ConnectionType = "0"
    doc.LanPortName = "TCPIP"
  Case Is = "Passthru" :
    doc.ConnectionType = "2"
  End Select
  
  doc.Destination = servername.Canonical
  If location <> "" Then doc.ConnectionLocation = "*" Else doc.ConnectionLocation = location
  doc.Source = "*"
  doc.OptionalNetworkAddress = tcpip
  Call doc.ComputeWithForm(True,False)
  Call doc.save(True,False,True)
End Sub

ALA: Sensible Forms
A List Apart has a new article which touches on some rudimentary do's and don't's for web forms. Most of the thing that are talked about are not something that I usually have to worry about as a Domino developer, but there are good things too keep in mind when developing for the web.
Computers are supposed to make our lives easier, not more difficult. As usability-conscious designers, we can make our users’ lives easier by thinking about the way people interact with our websites, providing clear direction, and then putting the burden of sorting out the details in the hands of the computers—not the users.

via Sensible Forms: A Form Usability Checklist

A new way of looking at a Notes Database
I ran into a problem with a database last week that caused me to look at Notes in a different way. I thought the solution might be of interest to some other developers out there.

The problem in question had to do with the Shared Actions in Notes 6.5. Somehow, the Shared Actions on one of the replicas of the database I was working on didn't look like it should. I tried all the normal things to get the changes in the design to update. I cleared the replication history and replicated from every other replica I could find. I created a template of the design, replaced the design of the database with a different template, and then replaced the design again with the template I had just created. Finally, after banging on it for over an hour, I deleted the replica in question and replicated a new copy from one of the other servers. All better, right? WRONG!

Lo and behold, the same problem existed in the brand new replica I just created. It seems that nothing I did to the Shared Actions on the other servers had any impact on the server that was having the problem. My initial theory as to the cause of the problem was a Save and Replica Conflict of the Shared Actions design element. I had seen this happen to other design elements in the past, namely views and forms. Those were easy enough to handle through the Designer UI since they were listed in a view like fashion, but there was no easy way for me to see the Shared Actions conflict. Or was there???

The solution to my problem was to change the $FormulaClass property of a view to allow the view to display design elements in a view. Yes, you read that right, you can display design elements as if they were documents in a database. For me, the beauty of Notes has always been that data and design have always been kept separate. A field on a document doesn't ever have to exist on a form nor does a document ever have to show up in a view index. What I didn't really grasp until I had this problem is that all Notes Design elements are just Notes in the database. If you can get a handle on it with a NoteID or NotesURL, you can do almost anything to it that you can do with a regular document. A 1999 DominoPower article by Dan Velasco and more recent blog entries by Vince Schuurman and Julian (of course) gave me all the information I needed to create the view I needed.

  1. Create a view with 3 columns - $Title (sorted), $Flags, and ModifiedDate
  2. Create an action button or agent to modify the view's $FormulaClass property
  3. Run the action/agent and rebuild the view (Shift-F9)
Below is the code for the agent I wrote:

Dim session As New NotesSession
Dim db As NotesDatabase
Dim view As NotesView
Dim doc As NotesDocument

Set db = session.CurrentDatabase
Set view= db.GetView("Admin\Design")
Set doc = db.GetDocumentByUNID(view.UniversalID)
Call doc.ReplaceItemValue("$FormulaClass", "32766") ' Shows all non-data notes
Call doc.Save(True, True,True)

Now, I could see that there were indeed 2 Shared Action ($ACTION) design elements. So, I thought, I'll just select the one I don't want and hit delete to remove it forever. WRONG! Doing that through the Client UI gave me an oh-so-helpful error, #02:70. So, after chatting my issue up with Rocky, I created a LotusScript agent that allowed me to enter the NotesURL of the bad $ACTIONS Note and delete it. Plus, I now have a kewl view that I can add to all of my databases to get a look at all of the design elements in one view. I think I will have to do a huge @Replace for the $Flags column to get a readable description of what the element is.

Daily WTF: Notes ©Function gone bad
It's good to see that Notes isn't left out of the discussion on The Daily WTF

It seems that whenever I tell a Lotus Notes programmer about this site, their reaction is consistently, "you must have a lot of Lotus Notes code posted !" Of course, this is not to say that Lotus Notes is a bad language (I've never used it), but it is odd that I keep hearing this.

Even Damien took a couple of seconds to add to the conversation. I just wish that some of the other responders were a little more informed and had something more to say than "Notes sucks".

via The Daily WTF - Listing a List

Yet another way to identify something in Notes
While working with the ASND Export Utility, I came across a new NotesSession method and 2 new properties that most data and design elements have. I have no idea how long they have been available, but like everything else in this world, I only found out about them when I needed a new way to do something.

The 2 new properties are more like 2 faces to the same thing. The HttpURL and NotesURL are the improved siblings of the UniversalID property. They both contain all the information you need to get to a particular document or design element, but the use different protocols to get there, namely http:// and notes://, respectively. The URLs generated for both properties use UNIDs instead of database and view names. The easiest way to get an idea of what they look like is to bring up the properties dialog for a document and click on the Meta tab. In case you don't know which one that is (I didn't), it's the one that is a plus sign (+) inside of angled brackets. The Identifier field at the bottom of the dialog is the NotesURL for that document.

Now, I know what you are thinking. What's the big deal if you already have hooks into the database and the view. The big deal is when these properties are used with the Resolve method of the NotesSession class. notessession.Resolve(NotesURL) will return a handle to whaever the NotesURL points to, whether it's a design element or a document. Imagine the generic subs and functions you can write if all you need the one little string to get a handle on whatever you need.

For me, this was important when returning a list of agents from a database. I didn't want to use the name of the agent, cause that could change over time. It was less likely that the NotesURL would change short of the agent getting cut and pasted back into the database. I have a feeling I will be using it these new features more and more in the near future.

Using an Array to get parts of the URL
Often when I am developing a web site, I will need to get a handle on different pieces of the URL. Sometimes I will need just the database path and other times I might need the document ID. I came up with an easy function that uses the split method to get the URL into an array and retrieve whatever piece of it I needed. The function below is built to work with Quickplace databases:

function getHomeFolder(returnValues) {
  var temp = new Array();
  temp = document.location.href.split('/');
  var rv = "";

  var protocol = temp[0];
  var server = temp[2];
  var qp = temp[3];
  var sub = temp[4];
  var db = temp[5];
  var view = temp[6];
  var doc = temp[7];
  
  if (returnValues.indexOf('1') != -1) { rv = rv + protocol + '//';}
  if (returnValues.indexOf('2') != -1) { rv = rv + server + '/';}
  if (returnValues.indexOf('3') != -1) { rv = rv + qp + '/';}
  if (returnValues.indexOf('4') != -1) { rv = rv + sub + '/';}
  if (returnValues.indexOf('5') != -1) { rv = rv + db + '/';}
  if (returnValues.indexOf('6') != -1) { rv = rv + view + '/';}
  if (returnValues.indexOf('7') != -1) { rv = rv + doc;}
  return rv;
}

The above function could be easily generalized to be able to be used in any database on a Domino server or any regular web page. It's much easier than looping through the href using indexOf() or substring(). In fact, you can use split to create a JS version of Rocky's favorite @Function, @Word.

function atWord(fullstring, sep, wordno) {
  var temp = new Array();
  temp = fullstring.split(sep);
  if (temp.length > wordno) {
    return temp[wordno - 1];
  } else {
    return false;
  }
}

How to Hack CSS: The Opening of Pandora’s Box
CSS hacks are a necessary evil for today's web developers. The father of CSS Hacks, Tantek Çelik, has an entry on The Web Standards Project concerning the mothodology that developers should use when coming up with hacks.
A CSS hack should (or MUST in the RFC2119 sense if you prefer):
  1. Be valid. Invalid hacks are unacceptable. Back in the heyday of Web 1.0 (i.e. the late 1990s), the The Web Standards Project and numerous others were already spreading the message of better coding through validity, and thus hacks themselves had to validate as well. (Nevermind that many/most so-called "Web 2.0" sites can't be bothered to validate. See above about the real professionals having to repeat the message).
  2. Target ONLY older/frozen/abandoned versions of user agents / browsers. When the Box Model Hack was introduced, we were already playing with betas of IE6/Windows (back when we expected there would be an IE6/Mac), and so we knew how to make sure it wasn't affected. And now we're playing with betas of IE7.
  3. Be ugly. It's actually a good thing that a hack be visually ugly from a coding aesthetic point of view in the hopes that the ugliness will be a reminder that the hack is a hack, and should incite a tendency for people to a) minimize it's usage, and b) remove it's usage over time. At it's core, browser switching is one of those things you really shouldn't, but must, do to get your job done. Hacks' ugliness are like the equivalent of persistent warning tags, a reminder to dispose of them when no longer necessary.
Tantek goes on to correctly state that IE7 will NOT remove the need for such hacks. What is most interesting about the article is to see how the hacks and the need for them evolved over time.

What really amazes me is that browser programmers and email vendors continue not to fully support the CSS2(.1) specification. The lack of support is more troubling with the emergence of Firefox as a true, and some would say better, alternative to IE. With the death of Netscape earlier this decade, most developers began to be able to develop for the IE platform only. While this may not have been a perfect platform to work with, it's flaws were well known and you could easily work within those limitations to accomplish what you wanted to. Now, you have to code to the standard and then hack the CSS to get it to work in IE. This can be extremely frustrating, especially when things look so much better in Firefox.

via Scobleizer

Symmetric Encryption Keys?
I had 2 emails from recruiters today that listed Experienced with symmetric encryption keys in Notes as a required skill. I was not familiar with that term so I went to the Help documentation in Notes and couldn't find it there either. After doing some searching on the net, I figured out that a symmetric encryption key is nothing more than a private key. Why couldn't they just say that??? Why do recruiters and managers have to make the job postings either incredibly hard to understand or impossible to fill due to the extensive list of irrelevant requirements? I mean how many people do you know who are an expert in Notes and an expert in Oracle, SAP, or .net? These days there is just too much to know to be truly proficient in multiple disciplines, unless the secondary subjects are directly related to your main knowledge set. In that case, you may be knowledgeable in that second area, but it is usually only when relating to the main area of expertise. For example, I know a bit about working in COM with Office, but that's only when I am writing LotusScript in a Notes Database. Get me in an Office only situation and I know only enough to be dangerous.
If you can’t beat ’em, use ’em
In my eyes, Gary Devendorf is much like Darth Vader. That is, even though he's gone over to the dark side, there's still a piece of him deep inside that is light and knows what is right.
I've been talking to countless Notes customers. One thing I tell them always surprises everyone in the room. "What you need is a Domino 7 server and Domino Designer 7." More that once the Microsoft sales folks got big eyes or chocked. So why would I say this? Its simple, most Notes customers have a large investment in Notes applications and data. The quickest way to write cool .NET applications that are relevant for end users is to include functionality in existing systems. And the easiest way to include functionality from one system to another is with web services.
Even though his bosses would rather it be different, he knows the power of the Domino platform and ability that it has to adapt and play well with other applications. Domino 7's ability to easily build web services may turn out to be a killer feature that changes Domino's fortunes for a number corporations.

via Domino 7 server - Microsoft says you need one

SearchDomino.com guides you thru the Eclipse
SearchDomino.com has put together a list of on-line guides to the Eclipse Development Platform.
Eclipse is an open source community whose projects are focused on providing an extensible development platform and application frameworks for building software. Eclipse provides extensible tools and frameworks that span the software development lifecycle, including support for modeling, language development environments for Java, C/C++ and others, testing and performance, business intelligence, rich client applications and embedded development.
The guides include are quite varied and should help you learn more about Eclipse, whether you've never even heard of it or you've been working in it for years.

via SearchDomino's Eclipse Learning Guide

Using LotusScript’s Execute command to save redundant code
Being the ultra lazy developer that I am, I truly hate writing redundant lines of code. I ran into this recently with a client when I was writing a report. When writing reports, I have become a true fan of using Lists, but the lack of being able to use a subscript to step through the values of the single dimension array can be a bit of an issue. While this is a mild inconvenience, it certainly became a real pita. The problem wasn't with accessing any of the values in the List, but rather figuring out which List to add a specific value to. Most of the time, I just use a Switch-Case statement to get a handle on the List I want. That's fine when you have 3 or 4 lists, but when you have the 13 lists I had to work with, I thought that there had to be a better way. In the end, I was able to use a couple of Execute statements to reduce 41 lines of redundant code to 2 lines of easily modified code.

In case you have never used the Execute command, it is used to compile and run a text expression at run time. In other words, things like variable names and subroutine calls can be determined at run time. This was important to me because the names of 2 sets of 13 Lists I had were almost identical except for a number. So here is the code as it would have been written using normal Switch-Case statements:

  Select Case monthnumber
  Case 1 :
    If Iselement(month1orders(ccode)) Then month1orders(ccode) = month1orders(ccode) + oqty Else month1orders(ccode) = oqty
    If Iselement(month1total(ccode)) Then month1total(ccode) = month1total(ccode) + ocost Else month1total(ccode) = ocost
  Case 2 :
    If Iselement(month2orders(ccode)) Then month2orders(ccode) = month2orders(ccode) + oqty Else month2orders(ccode) = oqty
    If Iselement(month2total(ccode)) Then month2total(ccode) = month2total(ccode) + ocost Else month2total(ccode) = ocost
  Case 3 :
    If Iselement(month3orders(ccode)) Then month3orders(ccode) = month3orders(ccode) + oqty Else month3orders(ccode) = oqty
    If Iselement(month3total(ccode)) Then month3total(ccode) = month3total(ccode) + ocost Else month3total(ccode) = ocost
  Case 4 :
    If Iselement(month4orders(ccode)) Then month4orders(ccode) = month4orders(ccode) + oqty Else month4orders(ccode) = oqty
    If Iselement(month4total(ccode)) Then month4total(ccode) = month4total(ccode) + ocost Else month4total(ccode) = ocost
  Case 5 :
    If Iselement(month5orders(ccode)) Then month5orders(ccode) = month5orders(ccode) + oqty Else month5orders(ccode) = oqty
    If Iselement(month5total(ccode)) Then month5total(ccode) = month5total(ccode) + ocost Else month5total(ccode) = ocost
  Case 6 :
    If Iselement(month6orders(ccode)) Then month6orders(ccode) = month6orders(ccode) + oqty Else month6orders(ccode) = oqty
    If Iselement(month6total(ccode)) Then month6total(ccode) = month6total(ccode) + ocost Else month6total(ccode) = ocost
  Case 7 :
    If Iselement(month7orders(ccode)) Then month7orders(ccode) = month7orders(ccode) + oqty Else month7orders(ccode) = oqty
    If Iselement(month7total(ccode)) Then month7total(ccode) = month7total(ccode) + ocost Else month7total(ccode) = ocost
  Case 8 :
    If Iselement(month8orders(ccode)) Then month8orders(ccode) = month8orders(ccode) + oqty Else month8orders(ccode) = oqty
    If Iselement(month8total(ccode)) Then month8total(ccode) = month8total(ccode) + ocost Else month8total(ccode) = ocost
  Case 9 :
    If Iselement(month9orders(ccode)) Then month9orders(ccode) = month9orders(ccode) + oqty Else month9orders(ccode) = oqty
    If Iselement(month9total(ccode)) Then month9total(ccode) = month9total(ccode) + ocost Else month9total(ccode) = ocost
  Case 10 :
    If Iselement(month10orders(ccode)) Then month10orders(ccode) = month10orders(ccode) + oqty Else month10orders(ccode) = oqty
    If Iselement(month10total(ccode)) Then month10total(ccode) = month10total(ccode) + ocost Else month10total(ccode) = ocost
  Case 11 :
    If Iselement(month11orders(ccode)) Then month11orders(ccode) = month11orders(ccode) + oqty Else month11orders(ccode) = oqty
    If Iselement(month11total(ccode)) Then month11total(ccode) = month11total(ccode) + ocost Else month11total(ccode) = ocost
  Case 12 :
    If Iselement(month12orders(ccode)) Then month12orders(ccode) = month12orders(ccode) + oqty Else month12orders(ccode) = oqty
    If Iselement(month12total(ccode)) Then month12total(ccode) = month12total(ccode) + ocost Else month12total(ccode) = ocost
  Case 13 :
    If Iselement(month13orders(ccode)) Then month13orders(ccode) = month13orders(ccode) + oqty Else month13orders(ccode) = oqty
    If Iselement(month13total(ccode)) Then month13total(ccode) = month13total(ccode) + ocost Else month13total(ccode) = ocost
End Select

Even though it's valid script, that's a lot of code that is almost identical. And if the formula I am using changes, it will take quite a while to modify all those lines of code. And I don't even think what a pain it would be to add another list to the mix or try to compile more than 13 months worth of data. Contrast the code above to the elegant 2 lines listed below:

Execute |If Iselement(month| & monthnumber & |orders("| & ccode & |")) Then month| & monthnumber & |orders("| & ccode & |") = month| & monthnumber & |orders("| & ccode & |") + | & oqty & | Else month| & monthnumber & |orders("| & ccode & |") = | & oqty
Execute |If Iselement(month| & monthnumber & |total("| & ccode & |")) Then month| & monthnumber & |total("| & ccode & |") = month| & monthnumber & |total("| & ccode & |") + CDbl(| & ocost & |) Else month| & monthnumber & |total("| & ccode & |") = CDbl(| & ocost & |)|

A couple of things to notice about the code above. You can't just reference any of the local variables. You have to convert them to text and use the value of the variables to created the text expression. However, you can change the value of global variables that are referenced in the text expression. I also used the Execute command with a For loop to write some code that manipulates an Excel spreadsheet.

For x = 4 To 29
    Execute |xlSheet.cells(| & thisrow & |,| & x & |).FormulaR1C1 = "=SUM(R| & startrow & |C| & x & |:R| & endrow & |C| & x & |)"|
Next

This is where using the R1C1 formulas really come in handy. Instead of writing 26 lines of code that each write a single formula to a single cell in a spreadsheet, I was able to write 3 lines of code that can do the same thing. I love it when a plan comes together!!! One word of caution before you go Execute crazy. I have no idea what kind of performance impact will be paid for the increased manageability of your code. Leave a comment if you have some actual numbers.

Color Palettes
Regardless of whether or not you like my new color scheme, there are a couple of sites that I use to come up with color palettes that I think might be helpful for you.

Color Scheme Generator 2 from WellStyled - Very easy to use color scheme maker. Allows you to either select a color from a visual palette or enter the RGB for a color. Returns complimentary and contrasting colors and allows you to select only web-safe colors.

Color Palette Creator from slayeroffice - It will create 10 shades of the base color, located top-left, at varying degrees of opacity. Along with giving you some nice color combinations, the author also provides the JavaScript and CSS source for easy dissection.

I think that any tool that can help us Domino developers create better looking applications is manta from Heaven.

Using Server-side Includes
The hardest thing for me to do as a developer is to go from a rich environment like Domino to a regular web server with standard HTML files every where. Every time I think about doing something cool to minimize the administration of the site, the first thing that comes to mind is how to do it in Domino. Well, I have started to work with Server-Side Includes (SSI) while I have been redesigning the CGC Builders site I maintain for my brother-in-law.

For those of us who bleed yellow and black, you can think of SSIs as the subforms of the HTML world. Although SSIs do have a performance cost, the productivity gains will be similar to what you have seen with subforms. They can either be a static or computed link to a separate HTML file, like a footer that you want on all pages, or they can be a formula for showing information like the last time the file was modified. The first thing you will notice about sites that use SSIs is that their pages end in either a .SHTM or .SHTML extension. These extensions tell the web server to look for SSI code in the HTML. Once you have the extension correct, just add a line like to your code. The above line inserts the contents of the footer.html file at that point in the HTML. On the CGC Builders site, I have used SSIs for the header, navigation, and footer.

It is important to note that the files that are included are not complete HTML files, but rather snippets of HTML. They do not have HTML, HEAD, or BODY tags in them, since that would cause problems if they were included in other HTML pages. There are a number of web resources out there for learning about SSIs. Here are a couple of the pages I used:

If you find yourself doing plain jane web development, SSIs are a nice way to maintain sanity in a sea of HTML files.

Love to mix the music up
I am in a heavy development/administrative mode right now. When times like this occur, I usually pop on my headphones, queue up my entire MP3 collection on the laptop, hit random shuffle, and push play. Due to my range of musical tastes, I really have no idea what will come up. For instance, Wildfire by Michael Martin Murphy is playing at this moment. It was preceded by Funkytown (Lipps Inc), Hotel California (Eagles), and Out Ta Get Me (Guns N' Roses). I always find it easier to code when I have good tunes to chair dance to, but I find that the hard stuff usually does best when really trying to code in bulk. It just gets the juices flowing and really allows me to crank stuff out. What about you?

While on the musical front, the lunchtime DJ on a local Rock station asked people to write in with their All-time Greatest Concert lineup. Basically, you choose 3 bands that you would like to see in a single concert. The bands do not have to have similar music or don't even have to have existed at the same time. After some hard thinking, here is my list:

  1. U2
  2. The Eagles
  3. Pink Floyd
The main problem I had when coming up with the list is that I have seen so many good concerts. It was tough for me not to have Melissa Etheridge, Bon Jovi, Elton John, Billy Joel, Bruce Springsteen, Eric Clapton, Madonna, Led Zeppelin, The Beatles, The Rolling Stones, Motley Crue, Depeche Mode, or many others on my list. I am going to see U2 for the 7th time at the MCI Arena in October. I am sure that the show will be as amazing as always.
Putting Reminders on user’s calendars
One of the most used databases at my company is our Company Communications Database. I built this database within the first 3 or 4 month of being hired and it has been a daily staple of the people in IT and HR. The core function of the database is to send out the mass mailings letting employees know about corporate events, system outages, and significant promotions. Emails are able to be sent out to users based on information in their person document, like department, region, and location. The great thing about this is that we don't have to worry about managing groups or worrying about people sending emails to everyone in the company about kittens they have for adoption. The information in the person document is updated on a weekly basis from our HR system.

A request that recently came to me was to put a reminder for an upcoming IT outage on the calendar for a specific list of users. To me, this seemed like a natural extension of the Company Communications Database. The form I used for setting up the reminder was just a modified mass email form that was already built. All the user has to do is select who gets the reminder, select when the reminder should go off, and enter the subject and body of the reminder. Below is the subroutine that actually puts the reminders on the user's calendar:

Sub SendReminder(person As NotesDocument, doc As NotesDocument)
  Dim mailfile As NotesDatabase
  Dim collection As NotesDocumentCollection
  Dim reminder As NotesDocument
  Dim item As NotesItem
  Dim body As NotesRichTextItem
  Dim oldbody As NotesRichTextItem
  Dim datetime As NotesDateTime
  
  Set mailfile = New NotesDatabase(person.MailServer(0),person.MailFile(0))
  If Not(mailfile.IsOpen) Then Exit Sub
  
  Set datetime = New NotesDateTime(doc.GetItemValue("calendardatetime")(0))
  Set collection = mailfile.Search(|Form = "Appointment" & ApptUNID = "| & doc.UniversalID & |"|,Nothing,0)
  
  If collection.Count = 0 Then
    Set reminder = mailfile.CreateDocument()
    reminder.Form = "Appointment"
    Set item = reminder.ReplaceItemValue("$Alarm",1)
    Set item = reminder.ReplaceItemValue("$AlarmOffset",0)
  '  Set item = reminder.ReplaceItemValue("$NoPurge",doc.GetItemValue("calendardatetime"))
    Set item = reminder.ReplaceItemValue("$PublicAccess","1")
    Set item = reminder.ReplaceItemValue("$CSVersion","2")
    Set item = reminder.ReplaceItemValue("_ViewIcon",10)
    Set item = reminder.ReplaceItemValue("Alarms","1")
    Set item = reminder.ReplaceItemValue("AppointmentType","4")
    Set item = reminder.ReplaceItemValue("ApptUNID",doc.UniversalID)
    Set item = reminder.ReplaceItemValue("Categories","Company Reminders")
    Set item = reminder.ReplaceItemValue("Chair","CN=Generic ID/O=Company")
    Set item = reminder.ReplaceItemValue("ExcludeFromView","D")
    Call item.AppendToTextList("S")
    Set item = reminder.ReplaceItemValue("OrgTable","C0")
    Set item = reminder.ReplaceItemValue("Principal","CN=Clark Construction/O=Clark")
    Set item = reminder.ReplaceItemValue("SequenceNum",1)
    Set item = reminder.ReplaceItemValue("UpdateSeq",1)
    Set item = reminder.ReplaceItemValue("WebDateTimeInit","1")
  Else
    Set reminder = collection.GetFirstDocument
    Set item = reminder.ReplaceItemValue("SequenceNum",reminder.SequenceNum(0) + 1)
    Set item = reminder.ReplaceItemValue("UpdateSeq",reminder.UpdateSeq(0) + 1)
    Call reminder.RemoveItem("Body")
  End If
  Set item = reminder.ReplaceItemValue("CalendarDateTime",doc.GetItemValue("calendardatetime")(0))
  Set item = reminder.ReplaceItemValue("EndDate",Datevalue(datetime.DateOnly))
  Set item = reminder.ReplaceItemValue("EndDateTime",doc.GetItemValue("calendardatetime")(0))
  Set item = reminder.ReplaceItemValue("EndTime",Timevalue(datetime.TimeOnly))
  Set item = reminder.ReplaceItemValue("StartDate",Datevalue(datetime.DateOnly))
  Set item = reminder.ReplaceItemValue("StartDateTime",doc.GetItemValue("calendardatetime")(0))
  Set item = reminder.ReplaceItemValue("StartTime",Timevalue(datetime.TimeOnly))
  Set item = reminder.ReplaceItemValue("Subject",doc.subject(0))
  Set oldbody = doc.GetFirstItem("Body")
  Set body = reminder.CreateRichTextItem("Body")
  Call body.AppendRTItem(oldbody)
  
  Call reminder.ComputeWithForm(True,False)
  Call reminder.Save(True,False,True)
End Sub

A couple of things that you should notice about the subroutine. First, a couple of NotesDocument objects are passed in, one pointing to the user's person document in the address book and the other pointing to the reminder document. Second, the agent is built to be able to update reminders that have already been created. The ApptUNID field will contain the UniversalID of the reminder document so that if the reminder document gets updated, the reminder on the user's calendar will also be updated, not duplicated. Finally, I only set the static calendar fields when the document is created. I think that I got all the required fields, but I might have missed one. Please drop me a line if you think I did.

Using Lists to write decent reports in Notes, part IV
Part I
Part II
Part III

Now, since we have used lists to gather the data, writing the data out to the Excel spreadsheet doesn't contain a single loop!! How cool is that?!? First, let me explain what some of the other subroutines that you have seen do:

  • SetExcelOptions - This is where I handle the page setup. I set the default font, page header and footer, and orientation.
  • SetWorksheetTitleRows - This is where the first row and column entries are set and where the format for the data columns are set. For most reports, I only set the top row, but since this report is a little more structured, I am also setting the first column on the first and third pages. The code in here sets not only the values for the cells, but also sets the look and feel for those cells. Here is some of that code:
    Dim thisselection As Variant
      
      With xlSheet
        .Name = "Overall Statistics"
        
        ' Top Header Row
        Set thisselection = .Range(.Cells(1,1),.Cells(1,8))
        With thisselection
          .NumberFormat = getNumberFormat("Text")
          .VerticalAlignment = xlTop
          .HorizontalAlignment = xlLeft
          .Font.Bold = True
          .Font.Italic = False
          .Font.Underline = False
          .WrapText = True
          .Font.ColorIndex = 1
          .Orientation = 0
          .MergeCells = False
          .Interior.ColorIndex = 15
          .Interior.Pattern = xlSolid
          With .Borders(xlEdgeBottom)
            .LineStyle = xlContinuous
            .Weight = xlThin
            .ColorIndex = xlAutomatic
          End With
        End With
        
        .cells(1,1).Value = ""
        .cells(1,2).Value = "Full Day Office Interview"
        .cells(1,3).Value = "Pass"
        .cells(1,4).Value = "Offer Made"
        .cells(1,5).Value = "Rejected Offer"
        .cells(1,6).Value = "Accepted Offer"
        .cells(1,7).Value = "Hit Ratio: Accepted vs Interviewed"
        .cells(1,8).Value = "Hit Ration: Accepted vs Offered"

    All of the "xl" values are constants that I set in the declarations section. By using the .Range to get a selection of cells, I am able to set the attributes of all those cells without looping through each cell. BTW, most of the Excel specific code was created by using the Record Macro function in Excel to create VBA that I wanted to mimic in LotusScript. Since the 3 sheets in this workbook as very different, I have 3 versions of this sub in the agent.
  • getNumberFormat - This gets the codes that set the correct number format for the cells. The code for text is "@" while the code for showing only Month and Year of a date is "mmm yyyy".
The ideas behind these subs have been developed over time and are not perfect nor are they set in stone. They are often modified to suit the needs of that application's requirements.

Once we have the sheets setup like we want them, we use the AddStatstoReport sub to populate the sheet. Here is a portion of that sub:

Sub AddStatstoReport
  Call AddLine(3,"Total")
  Call AddPercentLine(4)
  Call AddLine(6,"Company 1")
  Call AddLine(7,"Company 2")
  Call AddLine(8,"Company 3")
  Call AddLine(10,"Mid-Atlantic")
  Call AddLine(11,"Midwest")
  Call AddLine(12,"Northest")
  Call AddLine(13,"Northern California")
  Call AddLine(14,"Southeast")
  Call AddLine(15,"Southwest")
  Call AddLine(16,"West")

Each row is populated by calling the AddLine sub and passing the row number and the identifier. For the second sheet of this report, calling this Sub is done using a ForAll loop and a counter to calculate identifier and row number. Here is what the AddLine sub looks like:

Sub AddLine(row As Integer, listname As String)
  If Iselement(statint(listname)) Then xlSheet.cells(row,2).Value = statint(listname) Else xlSheet.cells(row,2).Value = 0
  If Iselement(statpass(listname)) Then xlSheet.cells(row,3).Value = statpass(listname) Else xlSheet.cells(row,3).Value = 0
  If Iselement(statoffer(listname)) Then xlSheet.cells(row,4).Value = statoffer(listname) Else xlSheet.cells(row,4).Value = 0
  If Iselement(statreject(listname)) Then xlSheet.cells(row,5).Value = statreject(listname) Else xlSheet.cells(row,5).Value = 0
  If Iselement(stataccept(listname)) Then xlSheet.cells(row,6).Value = stataccept(listname) Else xlSheet.cells(row,6).Value = 0
  xlSheet.cells(row,7).FormulaR1C1 = "=if(R" & row & "C2 <> 0,R" & row & "C6/R" & row & "C2,0)"
  xlSheet.cells(row,7).NumberFormat = getNumberFormat("Percent")
  xlSheet.cells(row,8).FormulaR1C1= "=if(R" & row & "C4,R" & row & "C6/R" & row & "C4,0)"
  xlSheet.cells(row,8).NumberFormat = getNumberFormat("Percent")
End Sub

Again, the IsElement function is used to see if the identified exists for each List and, if it does, the value for that identified is retrieved. Since a couple of the cells contain formulas that evaluate out to percentages, we also set the NumberFormat for those cells here. If you have never seen the FormulaR1C1 syntax before, I would suggest getting to know it since it is the easiest way to write Excel formulas from LotusScript.

So there you have it. With a little bit of ingenuity and some efficient coding, you can use Lists and Excel to create some really nice reports that anyone in your company can run. I have posted the code for the Agent in it's entirety for you to dissect at your leisure. Just be nice in your comment as I bruise easily.

Using Lists to write decent reports in Notes, part III
Part I
Part II

So, now let's take a look at the RecruitStats() sub and what do you know, it calls another sub to actually populate the lists.

Sub RecruitStats()
  Dim collection As NotesDocumentCollection
  Dim child As NotesDocument
  Dim intern As Boolean
  
  Set collection = recruit.Responses
  Set child = collection.GetFirstDocument
  intern = False
  
  Set child = collection.GetFirstDocument
  While Not(child Is Nothing)
    If child.form(0) = "Education" Then
      Call AddtoLists(recruit.offer_status(0),child.school(0))
      Call AddtoLists(recruit.offer_status(0),child.degree_type(0))
      Call AddtoLists(recruit.offer_status(0),child.degree(0))
      If child.degree_type(0) = "Engineering" Then Call AddtoLists(recruit.offer_status(0),child.engineering_type(0))
    End If
    
    If child.form(0) = "Internship" Then intern = True
    
    Set child = collection.GetNextDocument(child)
  Wend
  
  Call AddtoLists(recruit.offer_status(0),"Total")
  Call AddtoLists(recruit.offer_status(0),recruit.company(0))
  Call AddtoLists(recruit.offer_status(0),recruit.region(0))
  Call AddtoLists(recruit.offer_status(0),recruit.gender(0))
  Call AddtoLists(recruit.offer_status(0),recruit.ethnicity(0))
  Call AddtoLists(recruit.offer_status(0),recruit.contact_type(0))
  Call AddtoLists(recruit.offer_status(0),recruit.candidate_type(0))
  If recruit.International(0) = "Yes" Then Call AddtoLists(recruit.offer_status(0),"International")
  If intern = True Then Call AddtoLists(recruit.offer_status(0),"Intern")
End Sub

Again, because I didn't want to write more than I had to, I decided to put the actual population of this lists in a different sub called AddtoLists(). This sub is passed 2 variables, the current status of the recruit and the List identifier to use. This separate sub allowed me to consolidate all of my logic in one place and not have to copy and paste it all over the place. See, I told you I was lazy. So let's take a look at the AddtoLists() sub:

Sub AddtoLists(status As String, listname As String)
  Dim addint As Boolean
  
  If status = "Rejected Offer" Then
    addint = True
    Forall x In recruit.GetItemValue("reason")
      If x = "Prior to Interview" Then addint = False
    End Forall
    If addint = True Then
      If Iselement(statint(listname)) Then statint(listname) = statint(listname) + 1 Else statint(listname) = 1
    End If
  Else
    If Iselement(statint(listname)) Then statint(listname) = statint(listname) + 1 Else statint(listname) = 1
  End If
  
  Select Case status
  Case "Passed" :
    If Iselement(statpass(listname)) Then statpass(listname) = statpass(listname) + 1 Else statpass(listname) = 1
  Case "Offer Made" :
    If Iselement(statoffer(listname)) Then statoffer(listname) = statoffer(listname) + 1 Else statoffer(listname) = 1
  Case "Accepted Offer" :
    If Iselement(statoffer(listname)) Then statoffer(listname) = statoffer(listname) + 1 Else statoffer(listname) = 1
    If Iselement(stataccept(listname)) Then stataccept(listname) = stataccept(listname) + 1 Else stataccept(listname) = 1
  Case "Rejected Offer" :
    If addint = True Then
      If Iselement(statoffer(listname)) Then statoffer(listname) = statoffer(listname) + 1 Else statoffer(listname) = 1
    End If
    If Iselement(statreject(listname)) Then statreject(listname) = statreject(listname) + 1 Else statreject(listname) = 1
  End Select
End Sub

The first thing this code does every time we want to add sometime to the list is to check if the identifier in question already exists in the list. This is accomplished by the calling the Iselement function. If it doesn't exist, then we set the List(identifier) equal to 1, otherwise add 1 to the value stored in List(identifier). IMHO, this is so much better than looping through arrays to find where a given identifier exists.

So basically, I gather a list of recruits and, for each recruit, add 1 to each statistical category that it corresponds to. Once this is done, it's time to put the statistics onto the finished Excel spreadsheet.

Link to Part IV

Using Lists to write decent reports in Notes, part II
So as last we left our programming hero, I was describing what exactly List elements are. Now it's time to show you how they work in the real world.

We recently deployed a database to track our college recruiting activities. Along with a calendar on which college recruiting fair we will be attending and contact information on the college career offices, the database also tracks all recruits that we invite for an interview in the office. For the recruits, we track information such as gender, ethnicity, contact information, school, major, graduation date, and recruiting status. There is also an area to add an copy of their resume, cover letter, and a picture. So naturally, the HR group would love to get some pretty reports with statistics showing how we are doing in hiring at different schools, across different ethnic groups, and if we are getting the recruits that we really want. Here is the recruiting dashboard report that they developed for me to populate with data.

While there are many ways to skin a cat, being the lazy programmer that I am, I always try to find the simplest solution to any problem I have. When thinking about gathering statistics on documents in a database, a large number of arrays is what comes to mind. In fact, the image of dealing with a number of multidimensional arrays invades my psyche and causes me to shiver uncontrollably. Well, maybe it just causes me to eat more chocolate. In any event, I have found that List elements sooth my mind and allow me to elegantly write code to do what the customer wants.

So, after looking at the report they wanted, I came up with a number of lists that I thought would be necessary. I made each of the statistical columns a List and used the row titles as List identifiers. In the end, I needed just 5 Lists in Declarations:

Dim statint List As Integer
Dim statpass List As Integer
Dim statoffer List As Integer
Dim statreject List As Integer
Dim stataccept List As Integer

These Lists match up to Total Inteviewed, Passed, Made Offer, Accepted Offer, and Rejected Offer. Now that I had my Lists, I needed to populate them. Here is the first part of the Initialize subroutine:

Sub Initialize
  Dim w As New NotesUIWorkspace
  Dim schools As Variant
  Dim flag As Variant
  
  On Error Goto olecleanup
  
  Set session = New NotesSession
  Set db = session.currentdatabase
  Set tmp = db.CreateDocument()
  flag = w.DialogBox("DashboardDialog",True,True,False,False,False,False,"Limit Results",tmp,True)
  If flag = False Then Exit Sub
  
  Set recruits = db.Search(tmp.searchstring(0),Nothing,0)
  If recruits.Count = 0 Then Exit Sub
  Set recruit = recruits.GetFirstDocument
  
  Set xlApp = CreateObject("Excel.application")
  xlApp.Visible = False
  xlApp.Workbooks.Add
  Set xlSheet = xlapp.workbooks(1).worksheets(1)
  Call SetExcelOptions
  Call SetWorksheetTitleRows  
  
  While Not(recruit Is Nothing)
    Call RecruitStats()
    
    Set recruit = recruits.GetNextDocument(recruit)
  Wend
  
  Call AddStatstoReport
  
  Set xlRange = xlSheet.Range(xlSheet.Columns(1),xlSheet.Columns(6))
  Call xlRange.AutoFit

You can ignore most of the code above for now, but basically I retrieve a collection of recruits and then capture the stats for each recruit using the RecruitStats() subroutine. Note that recruits and recruit were also declared as Global Variables. It is in the RecruitStats() subroutine that the Lists are populated. Check back in my next installment to see what goes on in that sub.

Link to Part III
Link to Part IV

Using Lists to write decent reports in Notes
The biggest gripe I have always had with Lotus Notes is that it sucks as a platform to write reports against. Sure, I know that I could learn Crystal Reports and use it to write fancy reports, but then how do I get those reports out to users who don't have Crystal loaded on their machines. Back in the Version 3 days of Notes, they had this really cool product called Notes VIP that had a native report writer built in. Notes VIP was where LotusScript got it's start in the Notes world and was meant to be a visual front end to Notes databases in the days just prior to the Browser Wars. Any ways, the best thing about the report writer was that you could create canned runtime reports that could be run by anyone without needing to install any additional software. Alas, VIP went the way of Lotus Components and Lotus Improv. LotusScript was integrated into Release 4 and the reporting software was sold off to a third party vendor (Can't remember exactly which one!). How I long for product that could easily write reports without the need for additional software and tons of additional training.

While it may not be perfect, I have found Excel to be a poor man's replacement for such a product. Over the years, I have built a set of "Export to Excel" design elements that I shove into every database I have. This allows me to setup an export that users can run as often as they need to without having to know anything about the design of the database. The form that sets up the export can be seen here. This allows users to export their data to a familiar tool and then slice it and dice it however they please. However, this does nothing to give the executives the pretty reports they yearn for on a daily basis.

For that, I have learned to turn to LotusScript agents and specifically to the use of List elements. If you have never heard of Lists before, here is the definition from the Designer Help file:

A list is a one-dimensional collection of elements of the same data type. You can change the size of a list at any time while the application is running and LotusScript does not allocate any storage space at compile time for the elements of a list. Lists automatically shrink or grow when elements are deleted from or added to them. You access each element in a list by a unique String value, called a list tag.
Basically, it's an array that works the way arrays should work. You don't have to specify the number of elements in the array and you access the values of the array with an identifier instead of a number. If this doesn't make complete sense to you, come back for part 2 of this entry and see an agent I recently wrote that makes extensive use of Lists.

Link to Part II
Link to Part III
Link to Part IV