IBM®
Skip to main content
    Country/region select      Terms of use
 
 
   
     Home      Products      Services & solutions      Support & downloads      My account     
 
developerWorks
AIX and UNIX
IBM Systems
Information Mgmt
Lotus
New to Lotus
Products
How to buy
Downloads
Live demos
Technical library
Training
Support
Forums & community
Events
Rational
Tivoli
WebSphere
Architecture
Autonomic computing
Java™ technology
Linux
Multicore acceleration
Open source
SOA and Web services
Web development
XML
About dW
Submit content
Feedback


developerWorks  >  Lotus  >  Forums & community  >  Best Practice Makes Perfect

Best Practice Makes Perfect

developerWorks
A collaboration with Domino developers about how to do it and how to get it right in Domino

Keith Smillie had a question about the above error message, which occurred when he tried to add an "on behalf of" name to an agent by just creating a $OnBehalfOf text item.

The "On Behalf Of" value is stored in the agent design note item $OnBehalfOf, and is a text value. However, it is not valid to store a value in this item unless the "ODS version" of the design element is such as to support this feature. If you refer to the C API guide, the relevant structure is ODS_ASSISTSTRUCT, whose wVersion element must be 2 or higher. This binary structure is stored in the $AssistInfo item of the agent design note. At this time, there are no differences between the ODS formats besides this version number, but consult the latest API documentation to make certain.

There are a few different ways to fix this problem:

  • Edit the agent in Domino Designer.
  • Use LotusScript or Java to delete the $OnBehalfOf item from the agent note (You will need a NotesNoteCollection to get the note ID of the agent so that you can open it as a NotesDocument).
  • Use the C API for Notes to adjust the version number in the $AssistInfo item.
  • Export the agent to DXL, modify the DXL, and re-import it to update the agent.
Note: The latest version of the C API toolkit and documentation, is available in the Downloads section of the Lotus developerWorks website, under Downloads, Toolkits.

Andre Guirard | 12 May 2008 05:03:00 AM ET | Home, Plymouth, MN, USA | Comments (0) | Permanent Link

Everybody please read the new whitepaper on how to write well-performing Notes/Domino apps. I've pulled together information from a lot of sources, interviewed people in Iris, and generally tried to hit the most important recommendations and get the answers right. I hope this will become an important resource for developers at all levels of expertise -- I certainly learned a few things putting it together.

Andre Guirard | 9 May 2008 05:56:00 AM ET | Man-Cave, Plymouth, MN, USA | Comments (4) | Permanent Link

Since I've had to answer this question three times recently, I thought I would blog about it both because it seems to be something people are needing to do, and because that way I have something to send a link to, in future.

There are basically three ways to programatically make changes to design elements in LotusScript:

  • Use the methods and properties that are provided, e.g. NotesForm.FormUsers.
  • If the change you want to make is not supported that way, you can use DXL to export the design element, manipulate the DXL, then re-import it. As yet, this does not work with 100% reliability for all design elements, however, and still not all options are represented.
  • For some properties, you can access the design note with a NotesDocument object and change or delete items that store those properties.
It's the third technique I'm focusing on here.

The first step is to obtain the NotesDocument object representing the design element. If it's a folder or a view, this is how:

Dim db As NotesDatabase
Dim vu As NotesView
Set vu = db.GetView("viewname")
Dim docDesElem As NotesDocument

Set docDesElem = db.GetDocumentByUNID(vu.UniversalID)

If the design element isn't a Notes view, use a NotesNoteCollection to get the note ID. For instance, to locate the design note for an outline you might write:

Function GetOutlineNote(db As NotesDatabase, Byval outlineName$) As NotesDocument
        Dim nnc As NotesNoteCollection
      Set nnc = db.CreateNoteCollection(False)
      nnc.SelectOutlines = True
      nnc.SelectionFormula = {@lowercase($TITLE) = "} & Lcase(Replace(outlineName, Split({\,"}, {,}), Split({\\,\"}, {,}))) & {"}

       nnc.BuildCollection
      If nnc.Count <> 1 Then
              Error 31203, "Outline name " & outlineName & " matched " & nnc.Count & " outlines."
      Else

              Set GetOutlineNote db.GetDocumentByID(nnc.GetFirstNoteId)

      End If

End Function

Obviously, similar functions could be written for other types of design elements.

Once you have the NotesDocument representing the design element, you can mess with its items. For instance, the setting for whether a design element is protected from design refresh is the character "P" in the item $Flags. So if you wanted to unprotect design elements from refresh, you could do something like this:

flags = docDesElem.GetItemValue("$Flags")(0)
If Instr(flags, "P") Then
    docDesElem.ReplaceItemValue("$Flags", Replace(flags, "P", ""))
    docDesElem.Save True, False, True
End If

"How do we know to do that?" you might be wondering. One way is to download the C API toolkit from this website -- it includes a file stdnames.h, important parts of which are also copied here. This describes the purposes of all the $Flags values and some $FlagsExt values.

design element properties showing item valuesAnother way is to make the change you want manually, and compare the item values before and after to see what changed. From a view of design elements in Domino Designer press Alt+Enter or use menu File / Properties. Click the second tab to see a list of items in the design note.

Some items are of simple types -- number or text -- and you can replace their values as we did with $Flags above. Others, such as $AssistQuery in an agent, contain binary data which you'll be unable to decode from LotusScript -- but you can still delete the item, or replace it with a copy of an item from another design element of the same type (using CopyItemToDocument).

The other thing you must pay attention to, is whether the item you're modifying is signed. If it is, your modification will invalidate the signature of the design element, and you should re-sign it using the NotesDocument.Sign method before you save it. Otherwise Notes/Domino will refuse to use it. If you're not sure whether you're modifying a signed item, you can use the IsSigned property to check, or you can just sign the document anyway just in case (provided you're running your code with an ID that your users' ECL lists and so on will recognize).

In many cases, the design changes you make aren't noticed by the client until you exit and re-enter the application. The exception is outlines in version 8.0 and later -- there's a method to tell the client to reload these.

Have fun!

Andre Guirard | 7 May 2008 04:35:00 PM ET | | Comments (4) | Permanent Link

A while back I posted a class to provide a NotesStream attached to a temporary file. People responded with suggestions, and on thinking about it (and getting another question about it recently), it seems to me there's a need for a more general class for creating a temporary folder for your temporary files (so that you don't overwrite anything important by accident), and deleting it and the files when you're all done. If it has the ability to generate a unique filename at need, it's easy enough to attach a NotesStream to it if that's what you needed, plus it's useful for other things, such as detaching files from a document.

Accordingly, the attached LotusScript code, described below. The class will locate the Notes client's temporary folder (this requires a call to a C function but I'm handling multiple platforms -- if yours isn't there you can easily enough add it). The class creates a folder within the Notes temp folder and remembers to delete it when the object is deleted -- assuming that you have been good and either deleted all the files in the folder yourself, or told the class about them so it can delete them.

If would be possible to write a version of this that would use Dir$ to find any files left in the folder and delete them itself, but this would still fail if any of the files were still open. Also, Dir$ doesn't work so hot with some filename characters.

Please note: when constructing a filepath using the value of the Path method, use "/" as your directory delimiter. This works on all client operating systems, and I think all servers of recent vintage also.

Also note: as indicated above, it's up to you to close any open files before the TempFolderManager object goes out of scope, or they will prevent the folder from being cleaned up.

CLASS TempFolderManager

Properties

  • Path (String) returns the temporary folder path. This path is unique to this object; if you create a new TempFolderManager you get a new path. You can create your own filename path by appending a filename to this (following a directory separator character: "/" works for this on all OS).
Methods
  • New( )
    Creates a new temporary folder for you. When the object is deleted it will attempt to delete the folder, but if the folder is not empty that will fail -- that's sort of up to you.
  • Function NewFilename(Byval strSuffix$, Byval bManage As Boolean) As String
    Creates a new unique filename and returns its full path in your temp folder. The file is not created -- that's up to you -- just the name. If you specify a suffix, that suffix will be appended to the filename after a period. Don't include the period in the suffix yourself. If bManage is true, the file will be deleted automatically when this object is deleted.
  • Sub Manage(Byval strPath$)
    If you create your own filename in the temp folder and you want to make sure the file gets deleted when you're done, call Manage with the full filepath of the file. Take care -- if you call this method you are asking to delete the file whose path you pass in -- eventually.
  • Sub Unmanage(Byval strPath$)
    If the file is in the list of files to be deleted, remove it from that list. The file will not be deleted when you're done. The path must match exactly the path that appears in the list of managed files, or this won't work.
  • Function ClearFiles( ) As Boolean
    Delete all the managed files immediately. This returns True if all managed files were successfully deleted. This lets you reuse the same folder for more files without worrying about name conflicts -- for instance, if you were processing attachments from a set of NotesDocuments and you wanted to extract them all into a temp folder, but clean up after each document without having to create a new folder.
Download the code here:

Andre Guirard | 5 May 2008 05:30:00 AM ET | | Comments (0) | Permanent Link

I finally got tired of people whining that there was no way to make a view of profile documents. So, I created a way, which you can download from here -- no charge. It's a form which, when you open it, gets a list of all profile documents in the current database and shows them, letting you select one to edit, or multiple to delete. You can click the column headings to sort.

Profile document management screen

The edit function depends on having an appropriately named form or subform; if a subform, the thing opens in a dialog. Either way, you can save your changes. If you end up with duplicate-named profiles, you can still edit them all, but this tool will tell you if you're editing in the "normal document" way rather than via EditProfile (the latter will of course only work for one of the duplicates).

The delete has a confirmation, so that you don't permanently delete all your profiles by accident.

You can just paste this form into any application, and it should work in 6.0 on up (though 8.0 has a more efficient way to get the list of profile documents, which this code will take advantage of, if available). As usual, this is provided "as is," but if you run into any difficulties please let me know here.

The form is set to appear in the Create / Other dialog, so you can get to it without adding an action or outline entry, though of course it's a good idea to do that. I just wanted to have minimum effort to add it to an application.

I'm a big fan of profile documents, both for performance and because they make it simple to centralize configuration functions on a single screen. This tool should, I think, remove one more objection to their use. Along with the practice I mentioned earlier of checking for profile document existence in the database Postopen code before trying to use it, I see no reason anyone would want to use a dumb old lookup document for keyword lists, default rich text values, and so on, anymore.

P.S. Yes, Jamie, I know NoteMan will do the same thing with more features for free, but I wanted something that would be part of the application so that people wouldn't complain about all the developers having to install it. Huh.

Andre Guirard | 28 April 2008 05:15:00 AM ET | Home, Plymouth, MN, USA | Comments (8) | Permanent Link

I've written this book, see. It's about a little girl and her mole man buddies (and other friends) and the evil elves who threaten her and kidnap her parents, whom they then try to rescue. I sent letters of inquiry to a couple of agents, and one of them has responded asking to see the first 50 pages. I would of course have preferred she request the whole book, but this is progress!

Once I find someone to represent me, they have to then try to find a publisher, and then there's an involved process of getting it into print. And I am impatient.

And by the way, my wife just had an article accepted by Fine Gardening magazine. Yeah team!

Andre Guirard | 25 April 2008 08:38:11 AM ET | Home, Plymouth, MN, USA | Comments (5) | Permanent Link

A message in one of the dW forums recently discussed a problem they were having with the List datatype. Lists behave differently depending on whether you've selected Option Compare Nocase -- which is nice, because sometimes you like to have one that's not case sensitive. The problem is that if you use that same list in a different module with a different Option Compare setting, LotusScript doesn't remember whether the list is supposed to be case-sensitive, and just treats it according to its own settings. This can result in the data getting snarled up and being no use to any of the modules.

To avoid this problem, you have to make sure that all operations on the same list are carried out in modules with compatible Compare settings. If your settings vary between modules, you can arrange for all operations on that list to be carried out in the same module by creating a wrapper class that contains the list. Sample code appears below.

Class ListWrapper

        ' If you have to share a list among different LotusScript modules with different Option Compare
       ' settings, use this class to avoid the list getting snarled up and not working correctly for anyone.

       ' It will operate based on the Option Compare settings of the module where it is defined.

       Public BaseList List As Variant ' make it public so it can be used in Forall statements

       
       Function IsElement(tag As String) As Boolean

               Me.IsElement Iselement(BaseList(tag))

       End Function

       
       ' if the list elements are not objects, set and read their values using the Item property.

       Public Property Get Item(tag As String) As Variant

               Item = BaseList(tag)

       End Property

       
       Public Property Set Item(tag As String) As Variant

               BaseList(tag) Item

       End Property

       
       ' if the list elements are objects, set and read their values using the Object property.

       Public Property Get Object(tag As String) As Variant

               ' same as Get Item, but assume the item is an object (more efficient if you know this to be the case)

               Set Me.Object BaseList(tag)

       End Property

       
       Public Property Set Object(tag As String) As Variant

               Set BaseList(tag) = Me.Object

       End Property

End Class


So now if you want to keep a list of documents keyed by their note IDs, you might use statements such as:

Dim doclist As New ListWrapper
...

Set docList.Object(doc.NoteID) = doc

...

Set someDoc = docList.Object(keyNoteID$)

...

Forall aDoc In docList.BaseList


The get and set properties could be written to be more flexible, setting and returning either object or non-object values, but that would be less efficient. I'm assuming that the code that uses the list, knows what kind of data are in it -- and if not, they can do their own error trapping to deal with their peculiar mix of types.

By putting this in a script library with Option Compare Nocase, you can now have a list that's case insensitive, without having to have all your comparisons be case insensitive (in which case you might want to change the class name to reflect this).

Andre Guirard | 24 April 2008 06:45:00 AM ET | Home, Plymouth, MN, USA | Comments (0) | Permanent Link

Style 'setting' of a view columnThe image at the right looks deceptively like a property of the column -- but it is not. The type of data stored in a column is a non-concept, because data are not stored in columns in the view index. Data are stored in rows. The type of data stored for a column for a particular row, is determined by the formula of the column. It can be different for different rows.

It therefore makes sense to specify formatting for numbers (in case the value on a some rows happens to be a number) and dates (in case some rows contain dates) and names (because it might be a username string on some rows, or might pick up a name from another column). Because of the design of your application, you might know (or hope!) that all the values will be numbers, but that is not a property of the view design, but a statistical attribute of your data. Think of the Style field not as a property, but as a navigational control allowing you access to these different sets of formatting options. It is not the "datatype of the column," because no such thing can exist. Imagine instead of this field, a second row of tabs for number options, date/time options, and name options. It would've prevented misunderstanding if we'd done it that way at the start, but c'est la vie.

To get a correct picture of what's going on, one must think of the view's contents, and its formatting for display, as two completely separate things. If a column formula returns a number value for a particular document, then that exact number is stored in the view index; if a date/time value, that exact date/time is stored. For instance, suppose the formula for the Amount (K) column is TotalAmount/1000. If TotalAmount contains (the number value) -14506, the view index will store -14.506, expressed in binary floating point, to within the limit of accuracy of that representation.

Now suppose the settings on the "Advanced Format" tab of the column properties, specify two decimal places and parenthesis around negative numbers. How does this affect the value stored in the row entry? Answer: not at all. The sole purpose of these settings is to determine how the value will be displayed on someone's screen. -14.506 remains -14.506, even if when you open the view, you see "(14.51)" (or for some users, "(14,51)"). The column value retains whatever precision it had from the formula that calculated it (or the exact value from the original field, if it was a field column instead of a formula). It doesn't contain the character "(", or ".", or any characters. A number is the same no matter how you choose to display it.

You may be thinking, "Why does it matter, you picky Andre person?" Apart from an understanding of how things work being generally important to being able to predict what your design will do, there are three common situations where the difference between storage and presentation has important effects.

1.        When users with different formatting preferences use the view, the values are displayed using their local settings. As mentioned before, different users of the same view may see (14,51) or (14.51) depending what they have chosen to use as a decimal point character. Similarly with date formatting; the order of the year, month and day can be different for different users. This is highly desirable. It really is. Please don't try to defeat this useful feature by using @Text in the column formulas to convert the values to strings. If you do, you will confuse users, and make your data sort incorrectly (because 9 < 10 but "9" > "10").

2.        When you read the view data using code, for instance with an @DbColumn formula, or Columnvalues in LotusScript. These functions ignore formatting, going straight for the data. This is important both when reading the data, to realize that the value you read might not be exactly what's displayed in the view, and when specifying a lookup key, since it will only find documents that match the key exactly -- not that just look like they ought to match the key.

3.        Likewise, if you sort or categorize the view, you may see unexpected results if your column values have more accuracy than they display. For instance, suppose you sort ascending first by a column containing date/time values, then by a Name column. You might expect to see 04/23/2008 Alvin before 04/23/2008 Sam, but if Alvin's date/time is really 04/23/2008 4:15 PM and Sam's is 04/23/2008 7:40 AM, then Sam will list first despite your having used the formatting options to hide the time value. Similarly, suppose you categorize a view by date values that also contain a time (which is hidden). The date (the displayed value) may be the same, but the exact date/time is likely to be different for each document. This results in a view containing 50 categories for 04/23/2008, each containing one document, because while the category headings may look the same, they are not in fact the same. If you really need the entries to have limited precision, use the column formula to return an entry of limited precision (E.g. by using @Date(fieldname) instead of just fieldname ).

Andre Guirard | 23 April 2008 07:30:00 AM ET | Home, Plymouth, MN, USA | Comments (2) | Permanent Link

One thing that comes as an unpleasant surprise to folks who've grown up thinking in ASCII, is the default collation sequence in LotusScript. Because, you see, "A" < "b" < "C". This is nice when you're sorting a list of strings and like the de Veres to be somewhere in the neighborhood of the Dexters, but it has side effects you might not be aware of. For example, you might expect these expressions:

Case "A" To "Z"

If keyID Like "[A-Z][A-Z]###-## Then

to match only uppercase letters. In fact, they match all the lowercase letters also (except "a", which is less than "A").

There are probably a lot of applications out there that use expressions of this sort with the expectation that they're insuring the letters are the right case. It seems so obvious that it should that way, that people might not even think to test it. This is an example, if it were needed, of why one should test the obvious...

If you really want to check for just uppercase or just lowercase, you'll have to do one of two things:

  • List each separate character (keyID Like "[ABCDEFGHIJKLMNOPQRSTUVWXYZ][ABC...")
  • Use Option Compare Binary, which switches the collating sequence to one that groups all the uppercase letters in one bunch, and all the lowercase in another bunch.

Andre Guirard | 18 April 2008 10:59:59 AM ET | Home, Plymouth, MN, USA | Comments (9) | Permanent Link

This morning my wife showed me this article from the Star Tribune (no login needed) about the past days of air travel, and my attention was caught by the photo of an air stewardess entertaining the passengers (except, apparently, for one fellow), by playing music on an electronic organ on a 1959 flight.

How far we have fallen since those days! Live music on a flight! Why aren't they doing that anymore? And look how big the seats are! These days airlines seem to be focusing entirely on cutting costs, and so they take away and take away from their passengers. Pretty soon they'll rip out all the seats and have everyone stand -- they take up too much room sitting down. If you pack them in tight enough and put a huge long strap around the bunch of them, they won't fall down when you go through turbulence. I'm sure we could easily quadruple the number of people on a flight.

Someday soon there's got to be a rebound -- the customers will revolt. Really, musicians are not that expensive when you divide by the number of passengers on a flight (especially if you don't have seats), and wouldn't you prefer to take the jazz flight to New Orleans instead of the boring old economy flight? It's like starting your vacation early. Likewise the blues flight to Chicago (or they could do jazz too I suppose). Or the Broadway show tunes flight to New York? There might not be as many takers for the polka flight to St. Paul, though.

Andre Guirard | 17 April 2008 08:46:49 AM ET | Home, Plymouth, MN, USA | Comments (6) | Permanent Link

Lotus Software

Search this blog 

Blog powered by 

Disclaimer 

    About IBM Privacy Contact