Thursday, November 25, 2004

La Vie Sans Roses

(With apologies to Edith Piaf)

A funny thing happened on the way to Montreal — I found out that there were people who mattered to me.

As you may imagine (or not), there have been times in my life to this point when I have known loneliness. It would have been difficult for me to contemplate being more alone than, say, in those too many hours I spent desperately looking for a reason not to die when I was nearing the end of my life as a practicing addict. My homeless period was hardly one of the high points in my social life either. Still, what I felt then was nothing more than isolation. It's not the same thing.

For the first time in my life, I am feeling true loneliness. Loneliness isn't just a matter of having no-one around — I daresay that there are few people who pass any significant time without some kind of human contact — it's more a matter of having someone NOT around. Or several someones.

I've moved several times before, and I've been a stranger in a strange land more than once. I have left friends and acquaintances, knowing that they would fade into vague memory in less time than it takes to think about it. We all have lives full of people who seem significant while they're with us; we never find out the truth until they've been gone from sight awhile. Those have been my friendships in the past.

I expected that coming to Montreal would find me in the same place again — a few goodbyes and half-hearted promises to keep in touch, and before long the roster of friends and acquaintances would be back up to a full-strength team and the players traded away last season all but forgotten. That's the way it's always been before. Life has its surprises, though, and is generally a hell of a lot more complicated than it needs to be.

Maybe I've just lost the protection that youth affords. That's got to be it — I don't feel any less self-centred, arrogant or uncaring than before. Not that I'm a monster by any means, but my caring has always been a matter of convenience. You know — faceless charities I could care about in a detached and abstract sort of way, and I kept genuine human interest reserved for those in direct and immediate contact. "Out of sight, out of mind" has always been the norm. Right now, I'm wallowing in sentiment. There are a handful of people whose absence is sorely felt, and one who is making this change almost unbearably painful.

I don't suppose it helps at all that leaving Toronto and my old job behind finally gave me the chance to redefine that one relationship. There aren't too many rules I follow in life, but I've seen what can happen if you don't at least follow the old "don't shit where you eat" rule. (It may be more-or-less acceptable to use language like that these days, but I still find it horribly awkward. How were Borroughs, Thompson and Ginsberg able to get the pictures of their sixth-grade English teachers out of their heads?) There was one person at work, though, who I had grown quite fond of. Yes, that really is an appropriate choice of words — I'm not talking about a sudden enormous hormonal rush or anything like that. As much as I tried to keep things at a safe distance, with every thoughful word and gesture she slowly worked her way into my heart to the point that I'm now pretty much lost without her. The goodbyes put all of that on the table, and more.

Now for the complicated bit — not only are we separated by an unreasonable commute, there is the fact that she's married to deal with as well. (You knew that was coming, didn't you?) It's not so much that it's impossible to have the depth of feeling we have for each other under the circumstances, but it would be a whole lot easier to deal with if we could explore those feelings without having to consider the consequences. Even if she were to run headlong into this, I'm not sure that I could be mercenary enough to go right along with it — she stands to lose a lot more than I do, and the last thing I want is to see her hurt in any way.

(That's part of what caused the dilemma in the first place. All I wanted to say was that she had mattered to me, that all of the things she had thanked me for were merely given in response to her own incredible warmth and generosity of spirit. How much we mattered to each other was a subject that sort of grew as the artificial barriers between coworkers were erased. I should have said so much more so much sooner; she needed to hear it. Do you have anyone in your life, even at the outermost edge, who is just brimming over with love, kindness and caring, but who still manages to think of herself/himself as somehow unworthy of reciprocal attention? Tell them they matter before it's too late.)

Anyway, folks, if you've been wondering what's been keeping me too busy to blog, it has been hour upon hour of letter-writing, trying to keep the enormous holes in my life at least partially filled. (For those of you who haven't heard, I hate telephones with a passion few can imagine. Reach out and touch someone, my ass — all you can do is reinforce the fact that you cannot, in fact, touch them. When I picture Hell, it is very much like that.) And Jess, if you're reading this — you're a pretty special lady yourself. Thanks for checking up on me. I owe you a bucketfull of hugs. (And no, Matt, I'm not trying to add another one to the resume ;o) )

Wednesday, November 17, 2004

Redaction and Reaction

First, let me beg forgiveness for the original single-list posting. I let a search-and-replace get out of control, and all of the ":=" operators in the Formula Language portions were replaced with ":[space]=". (I needed to allow line breaks in list concatenations in order to make the web page flow and failed to use the "Selected Text" range option, in case you were wondering.) If anyone was doing a copy 'n' paste, you were likely scolded severely by Designer when you tried to save the code. That'll teach you to use code you just find laying around on the web, eh? The links were also all broken because they started with "http[space]:[space]//".

Second, I want to ask you to follow the link to Damien's site and read what he has to say about Formula Language's list handling, particularly in R5. He's in a unique position to know; he's the guy who rewrote the Formula Language engine for Notes and Domino 6. If you like things like @For, @While, @Transform and @Sort, he's the guy to thank. Send him money. While you're at it, find the name of the guy (or gal) who invented the window-screen-on-a-stick thing that keeps stuff in the frying pan from spooging up the kitchen, and send him (or her) money too.

You can make the dialog snappier by exploding a string of numbers rather than coding a list directly. I was not aware that, in the example I posted, 98 separate arrays were being created and 97 destroyed in memory. I had assumed that the initial declaration of a literal array/list would be dimensioned all at once, and that only additional items would take the equivalent of a Redim Preserve (or string mutation, for those who are familiar with how strings really work) hit. I like learning stuff like that; it makes me better at what I do, and what I do is often stuff that can be horribly expensive if I don't pay attention to details like that. Interesting, though, that the segment of the posting that caused my blog to break was the same piece that will improve my code from here on out. The OpenNTF R5 version will incorporate the improvement; the Notes 6 version will be using @Transform for numbering.

About being expensive — it's not that I go out of my way to write stuff that takes a lot of resources. I just hate to hear things like, "Notes can't do that." Notes usually can, and if it can't, then Domino on the web can. I'm not talking about writing highly transactional systems for international money transfers or anything like that. It's almost always about taking the things that Notes has proven itself to be good at, is already being used for, and moving some aspect of that to the next level. I don't have the skills (or, frankly, the ambition to gain the skills) to write low-level tools, so I'm stuck with Formulas, LotusScript, DHTML and my embarrassingly sparse Java. Often the initial implementation, while functional, is far from the best way of going about doing what I've done, but I'll release it anyway because it fulfills a business need. Just as often, I'm in uncharted waters, so I can't just ask how others have done it before. I can usually find a much better-cheaper-faster way for version 1.1, but I can also make sure that version 1.0 is as good as it can be given that it may be a fundamentally flawed approach. Paying attention to people like Damien (and others who know what they're talking about, like Ben and Rocky) helps make my bad code limp along at least a reasonable trot, and makes sure that my good code flies.

Monday, November 15, 2004

Sex and the single list

Okay, the "sex" part was a blatant attempt to court visitors. This has nothing to do with sex at all, I'm just including the word "sex" several times in the title and first paragraph of the article. This is about dialogboxes in the Notes client; specifically, a single ordered-list dialog.

The user will see a single-value listbox, three buttons to manipulate the listbox content, specifically "Move Up", "Move Down" and "Remove", an OS-style field to enter new content, an "Add to list" button, and the usual OK/Cancel pair. With these tools, the user can create and manage the content of a multi-value field, putting the values into any desired order without having to do any cutting, pasting or re-typing. Optionally, ND6 designers can add a "Sort" button, just in case the user wants alphabetical sorting.

In order to present that dialog, we are going to need some hidden stuff as well. We can't get to the choice list for a listbox directly, but we can make the listbox get its values from a field we can get to.

Create a form (or subform, if you prefer) and call it "(OrderedListDialog)". Set the form's background colour to "System" (use the little monitor picture on the colour picker). Why? Well, even though we can do pretty much whatever we want with colours, users expect to see dialog boxes that look like dialog boxes, and "System" does just that.

At the top of the form, create four always-hidden fields. (If these fields are not hidden, the content of the dialogbox may move lower in the dialog window as values are added to the list). The first field should be called "UseNumbers". It will be text, editable, do not select "Allow multiple values", and it will either contain "1" or "0". The second is called "Values", and will be text, editable, and this time do select "Allow muliple values". The third is called "StrippedValues". Set that to text, computed, multi-value and enter the following formula :

@If(UseNumbers = "1"; @Right(Values; ". "); Values)

The fourth and final field is called Numbers. It is text, computed, multi-value, and its formula is a hard-coded number list :

"01" : "02" : "03" : "04" : "05" : "06" : "07" : "08" : "09" : "10" : "11" : "12" : "13" : "14" : "15" : "16" : "17" : "18" : "19" : "20" : "21" : "22" : "23" : "24" : "25" : "26" : "27" : "28" : "29" : "30" : "31" : "32" : "33" : "34" : "35" : "36" : "37" : "38" : "39" : "40" : "41" : "42" : "43" : "44" : "45" : "46" : "47" : "48" : "49" : "50" : "51" : "52" : "53" : "54" : "55" : "56" : "57" : "58" : "59" : "60" : "61" : "62" : "63" : "64" : "65" : "66" : "67" : "68" : "69" : "70" : "71" : "72" : "73" : "74" : "75" : "76" : "77" : "78" : "79" : "80" : "81" : "82" : "83" : "84" : "85" : "86" : "87" : "88" : "89" : "90" : "91" : "92" : "93" : "94" : "95" : "96" : "97" : "98" : "99"

Yes, you could use three-digit numbers and go to 999. This is an R5 compromise; in ND6 you could just as easily use the new @Transform and @Member to add numbers to the results when numbering is required without hard-coding anything (again thanks, Damien) . If you're stuck with a hard-coded list in R5 (as I am right now), then don't just type it — do as I did, write a quick web form (a couple of clicks in TextPad will give you most of the page), create the string in a field using a JavaScript "for", then copy 'n' paste. Laziness is just another word for productvity if you know how to do lazy right.

Now create a table. Make it fixed width, five columns by nine rows. Oh, and make sure that it isn't hidden — users really, really hate blank dialogs. Select the whole table, and set all borders to zero (users don't have to know you're using a table, and only masochists would use layout regions). While everything is still selected, set the font to Default Sans Serif 9 point (smaller text looks better in dialogs, and the default sans-serif is usually the best option).

Go to the second row and merge the three centremost cells. This is where you'll add text such as "Select a value in the list below and use the buttons provided to move your selection." Leave a row blank. Go to the second cell in the fourth row, and merge it with the cell below it. This merged cell will contain the listbox. Create a listbox field called "SortSelection", single value, and make it two inches wide by three inches high (5cm by 7.5 cm), fixed, and set the font to Default Sans Serif 9pt. Select "Use formula for choices", and enter StrippedValues as the formula. Select "Refresh fields on keyword change" and "Refresh choices on document refresh".

Leave a row blank (yes, again) then in the second cell of the seventh row, type "Add New Value : ". In the cell below that, create an editable text field, OS style, two inches wide by 0.250 inches high, fixed, and set the font to Default Sans Serif 9pt. Call the field NewValue.

Now all we need is a bunch of buttons to play with. Go to the fourth column of the fifth row. Create six buttons (Create —> Hotspot —> Button), and put a carriage return between each button so they appear one above the other. Make them all fixed width, 2 inches (5cm) wide. The first three should have their font set to dark grey. Label them "Move Up", "Move Down", "Remove", "Move Up", Move Down", and "Remove".

I know — two inches looks pretty darned wide, but trust me, there is a reason for that. Now (and here's a really, truly kewl trick), go to the <HTML> tab of the button properties, and enter a name value for each (I use greyedMoveUp, greyedMoveDown, greyedRemove, activeMoveUp, activeMoveDown, and activeRemove). What we just did, class, was prepare the dialog for internationalization.

<Voice value="Isaac Hayes"/>
<Context value="South Park"/>
<Character value="Chef"/>
Children, you don't have to hand-code a thousand buttons with hide-whens or create a bajillion dialog forms just because LotusScript won't let you change the labels. Use JavaScript on your sweet lady dialog, and make love to her all night long. I have a song that might help...

Hey, I guess this is about sex after all! One of the few really useful things about the Notes client JavaScript object model is that buttons are accessible via document.forms[0].elements['ButtonName'], and that the .value of each is the button label text. You can change the value at run-time from the form's onload event (or anywhere else that makes sense). The English labels may fit neatly inside a 1.1-inch wide button set, but you'll need some room for French, Spanish or German.

<Rant>That little discovery was made entirely by accident, previewing a form meant for the web in the Notes client. It's not documented anywhere except in a couple of postings I made on the developerWorks fora (Notes.Net), but dammit, it should be. In big, bold, red letters on the default opening page of Designer Help. I wonder how much time and effort has been wasted by developers creating multilingual applications because stuff like this isn't documented. It's not just dev time, you also create a maintenance nightmare when you have several parallel forms/pages that have to stay in sync, or even several buttons that should be identical except for the label on the same form/page.</Rant>

The Notes client won't let you create mutable JavaScript (that is, you can't change the code by injecting a formula like you can on the web), so you'll need to add one or more hidden fields to carry either a language value or the actual label values themselves. Similarly, the help text can be computed to whatever language the user requires (in Formula Language, of course).

Select the greyed buttons and set the hide-when to !(SortSelection = ""). Select the lower three (the ones with black text) and set their hide-when to SortSelection = "". This way, the user will not see what appear to be active buttons until they make a selection in the list. The top three buttons don't need a formula of any kind, cuz they don't do anything. The bottom three are the real ones. Ideally, you'd like to hide "Move Up" when the first value is selected and so forth, but gimme a break! I've done enough figgerin' for you already.

Go to Remove first. The formula for this button is simply :

tempValues : = @Trim(@Replace(StrippedValues; SortSelection; ""));
FIELD UseNumbers : = "1";
FIELD Values : = @Subset(Numbers; @Elements(tempValues)) + ". " + tempValues;

Note that we've set the UseNumbers field to a "true" value. The final list is available in two versions, numbered and unnumbered. If you want the unnumbered version, you use the StrippedValues field of the dialogged document, if you want numbers, use Values. It's all about reusability — you shouldn't have to change anything on the form to plug it into a new application. The values in the listbox are always unnumbered in this version of the dialog. My tests have shown that users find changing numbers in the dialog less comfortable than an unnumbered list. Your mileage may vary.

Move Up and Move Down are a bit more complicated, but not much. We have to determine where the slection is in the list, then figure out the parts that won't change, then switch the selection with the value above or the value below. Getting the position is just a matter of using @Member against the whole list. Remember, we're getting the list from StrippedValues, so the first line of each formula will be :

position : = @Member(SortSelection; StrippedValues);

In Move Up, the button should do nothing at all when the selection is at the top already, so the next line would be :

@If(position = 1; @Return(""); "");

Now we need to know how big the total list is :

count : = @Elements(StrippedValues);

Next, we'll need the list of values below the selection that will not change. If the selection is at the bottom, this list will be empty, otherwise, we'll take a subset :

bottom : = @If(position = count; ""; @Subset(StrippedValues; - (count - position)));

Similarly, we need the stuff at the top that won't change. Keep in mind that the value immediately above the selection will be swapped :

top : = @If(position = 2; ""; @Subset(StrippedValues; position - 2));

Finally, we'll get the swap value :

swap : = @Subset(@Subset(StrippedValues; position - 1); -1);

Now we can assemble the values and complete the action :

tempValues : = @Trim(top : SortSelection : swap : bottom);
FIELD UseNumbers : = "1";
FIELD Values : = @Subset(Numbers; @Elements(tempValues)) + ". " + tempValues;

Using the same kind of logic, the MoveDown formula becomes :

position : = @Member(SortSelection; StrippedValues);
count : = @Elements(StrippedValues);
@If(position = count; @Return(""); "");
bottom : = @If(position = count - 1; ""; @Subset(StrippedValues; - (count - (position + 1))));
top : = @If(position = 1; ""; @Subset(StrippedValues; position - 1));
swap : = @Subset(@Subset(StrippedValues; position + 1); -1);
tempValues : = @Trim(top : SortSelection : swap : bottom);
FIELD UseNumbers : = "1";
FIELD Values : = @Subset(Numbers; @Elements(tempValues)) + ". " + tempValues;

One more button to go. "Add to list" should not only add a new value to the list, it should also select the value so that the user can move it immediately without having to manually select it. The formula is pretty simple :

@If(NewValue = ""; @Return(""); @IsMember(NewValue; StrippedValues); @Return(@Prompt([OK]; "Duplicate Entry"; "The value you are trying to add is already in the list.")); "");
tempValues : = StrippedValues : NewValue;
FIELD UseNumbers : = "1";
FIELD Values : = @Subset(Numbers; @Elements(tempValues)) + ". " + tempValues;
FIELD SortSelection : = NewValue;
FIELD NewValue : = "";

Now it's just a matter of formatting the table. Make columns 1, 3 and 5 0.125" (as narrow as you can in metric). Columns 2 and 4 should be 2"/5cm (or as close to this as Designer will let you get). Now, go to the cell just above the Move Up, etc., buttons, and change the text properties to Arial and increase the font size until the first three buttons are more-or-less centred vertically beside the listbox. You can use the same font-size technique to adjust the height of the "white space" rows we left in the table, but you'll need to select the whole row to make it work.

That's all there is to creating the dialog in its basic form. Now to put it to use. Since this is for re-use, and we can't be sure that the fields in the dialog even remotely resemble the item names in our application, we can't simply use @DialogBox. With LotusScript, you can dialog any document you want — even one that isn't really there. Create an agent, and call it CallSingleListDialog. Set it to run "Manually from the agent list" and (in R5) set the Documents to run against to "Run once (@Commands may be used) " or (in ND6+) set the target to "None". The code is not very complex. In this example, I will be calling the dialog from a document open in edit mode using @Command([ToolsRunMacro]; "(CallSingleListDialog) ") in an action that's hidden in read mode — the simplest scenario. In this case, the "real" document does not use numbering within the field.

Option Public
Option Declare
'Don't ever let me catch you not using
'Option Declare or Option Explicit

Dim s As NotesSession
Dim ws As NotesUIWorkspace
Dim thisDb As NotesDatabase
Dim currentUIDoc As NotesUIDocument
Dim currentDoc As NotesDocument
Dim dialogDoc As NotesDocument
Dim success As Variant

Sub Initialize
Set s = New NotesSession
Set ws = New NotesUIWorkspace
Set thisDb = s.CurrentDatabase
Set currentUIDoc = ws.CurrentDocument
Set currentDoc = currentUIDoc.Document
Set dialogDoc = thisDb.CreateDocument

Call dialogDoc.ReplaceItemValue("UseNumbers", "0")
Call dialogDoc.ReplaceItemValue("Values", currentDoc.GetItemValue("ExistingField"))
success = ws.Dialogbox("(OrderedListDialog)", True, True, False, False, False, False, "Manage List Values", dialogDoc, True, False)

If success Then
Call currentDoc.ReplaceItemValue("ExistingField", dialogDoc.GetItemValue("StrippedValues"))
End If
End Sub

Here endeth the Lesson. As I mentioned, the variation that will be posted on (after the fourth article in the series) will be somewhat more fully-featured and versatile, including code for internationalization and several examples of how to use the dialog. If you find errors or improvements to be made in the posted version, please use the comments feature to tell me about them — I do check, fix problems and incorporate upgrades in dot versions. If you find the code useful, use the rating system and (optionally) the comments to let other people know that the code is worth taking a look at. There's a lot of stuff in the Code Bin, and not all of it is as useful or necessary as the poster believes (some is just a lot of code to replicate a built-in feature, some is reposting of old routines, apparently so the poster can find it again later), and even I might be under unwarranted delusions of brilliance (but I doubt it). By the same token, if the code sucks, rate it as sucky. I'm not proud ... or tired. I just want to make sure that the Code Bin is and remains a worthwhile visit for's users and members. Sometimes I think a code review might be useful, but then it'd be just like the Sandbox, where a posting can take forever to appear — and I'm pretty sure that Bruce, Nathan, Anil and the gang have better things to do with their time.

Life as a Tête-Carré off Sainte-Catherine....

Well, I'm finally in Montréal, and I have to say that I like it so far. The only problem I've come across is that I actually have to go outdoors for twenty seconds twice a day — thirty if I need groceries. I found a tiny, but comfortable and quiet place for a better-than-reasonable price just a few feet off of the Maisonneuve Metro line. (Montréal's Metro is a sort of subway wearing sneakers — rubber-tired trains running in a trough rather than steel wheels on steel track. If you're not actually in the tunnel, you'd never know a train was running by every few minutes.)

What's surprising about all of this is that I'm in what would be considered a "bad neighborhood" in most cities. I live under a bridge. The building directly across the street houses a strip club. Oh, and it's not the only such establishment in the area, since I am a short block from the fabled Sainte-Catherine strip. With all that said, it is a much quieter life than I was used to in Toronto. The traffic on the bridge is a lot quiter than a TTC streetcar, and I have yet to run across yahoo-level drunkenness. I've only been here one weekend, but even though the strip sees a lot of action, people are calm and polite. There is a lot of retail mixed in with the sin spots, and overall it's not the kind of place that parents would steer their kids away from.

This is in very sharp contrast to, say, the Combat Zone in Boston or even the Entertainment District/Yonge Street in Toronto. I haven't heard a single siren yet, and it would be an odd evening in downtown Toronto if a whole hour passed between multi-car police responses passing by my window with lights and sirens blazing. Does Puritanism actually cause the problems we're used to?

Oh, yes — the food. If you happen to venture to Montréal in your jouneys, don't waste a lot of time looking for a good restaurant. As long as you stay out of the major fast food chains and mall food courts, you can eat well. The average greasy-spoon-looking hole in the wall will surprise you. These people take their food seriously, although they seem to have lost the concept of the single-serving portion somewhere along the line. If you eat all of your meals at restaurants and finish everything on your plate ("platter", "serving tray" or even "shipping palette" would be more accurate terms), you will need a complete new wardrobe every week.

There is one other small hitch. I've found that my French has completely atrophied. I can understand what people are saying (to a point — there's a lot that's new in the last twenty-odd years, so my childhood French didn't include any of it, the "in" slang has changed, and the patterns of speech here are a bit different from the Northern Ontario version of the language), and I can read well enough, but I'm having a devil of a time trying to talk. I can't think in French anymore, and it seems that my thought-formulation and translation routines can't multithread. And if you see my writing, you'd think that I had a random-number generator throwing accents and word endings in, but I never was much good at writing in French. It's been a long time since I spoke French on a daily basis (my mother's family is French, and a large part of the area I grew up in was francophone). Even then, my grandparents preferred that we "spoke white", and that if we wanted to learn French, we should learn proper French and not their slangue. That's all well and good, but the Elvis Gratton patois is invaluable in real life, and French is no longer a ghetto language in Canada (there was a time when you absolutely had to speak English in order to be promoted to any decent-paying job, even in a sawmill town where the nearest English community might be hundreds of miles away). It would be a pity to live life here in English. Not that it can't be done — at least downtown, you would have a hard time finding a place that won't serve you in English if that's what you need, and there are a lot of unilingual Anglos in the city. So far, I've had store clerks manage the transition for me, and only a couple have actually let me finish what I started. That's a bit frustrating; I'd have been better off in Chicoutimi as far as French immersion goes. I just think that I'd be missing out on a lot if I can't get my tongue in gear. And I would always be a tête-carré (literally, "blockhead" or "square head"; a derisive term for Anglos when there are no Germans around).

I don't know for sure when I'll be able to start posting more regularly; there's a lot to do before I can say I'm properly set up here. I'll try to keep you all as up-to-date as I can, but it may be a while before I can start posting at leisure. I have a couple of tech articles brewing, but they're not quite ready for bottling yet. To any of you who haven't removed me from your bookmarks and aggregators, I thank you for your patience and hope to reward you with something a bit more substantial soon.

Room for orderly dialog

There have been a couple of folks asking around about Notes articles. "When's the next article?" "Why haven't I seen any tech articles on your blog lately?" "Why are you not writing an article now?" "What's up, dude? Out of ideas? Are you just a one-trick pony? Do you only do calendars?" Well, I guess it's time we had an orderly dialog or two.

Every once in a while, you run across an application where you need things to appear in a particular order. I'm not talking about sorting — there are a thousand variations of sorting algorithms for LS out there, and Java has sorting methods built-in. Even Formula Language has a wonderfully fast and efficient @Sort now, thanks to Damien. No, I'm talking about creating an arbitrary order in a list, and making that list easily maintained by the user.

One of the first things that people bang their head against in Notes is the fact that the choice list for keyword-type fields is not programmatically accessible at run time. Anyone used to creating UIs in, say, Visual Basic will have become accustomed to being able to manipulate the contents of a listbox, and then using the entire content of that listbox elsewhere in the program. We Notes types take it for granted that this can't be done directly. Fortunately, the word "directly" gives us an escape route. There may be work-arounds involved, but we don't need to offer second-class UIs to our users.

Over the next few entries, I'm going to take a look at two variations on that theme. One is a simple single-list dialog that will allow the user to add and remove entries and shuffle the order of entries without having to cut 'n' paste or re-type. The second is a dual-list version that allows the user to select options from a source list, add them to (or remove them from) a second list, and put the result into any desired order. There will be two versions of each, one for the Notes client and one for the web. Once the series is complete, I will add the dialogs to's Code Bin (for those who are either too busy to cut 'n' paste or who want to see the full-featured versions, since some features will not be covered here for brevity — I'm long-winded enough already).

The Notes versions are interesting in that the entire dialog is driven by Formula Language. Oh, sure, the dialogs will be called by LotusScript, but that's only to create a temporary document so that you don't add a bunch of useless fields to the target document or worry about the field names on the dialog form. All you need to do is modify the calling script to suit the application.

I hope this satisfies the vultures for a little while. I know that the rest of you would be much happier reading about the life of a tête-carré in Montréal, so there will be some discussion of smoked meat, poutine, real bagels and the meaning of the "RC" button in the elevators between installations.