Friday, December 31, 2004

Perspective Vortices

The events of the past few days have certainly put my little problems into some perspective. I'm going to do something I have never done before, and pass along a bit of an editorial comment by the CBC's Rex Murphy. I can't recall his exact words, but they were very close to this: just by being born in the West, we have each already won the only lottery that really matters, and we have had the benefit of those winnings from the first time we drew breath.

I have had to do things to survive that many of you would find distasteful or even morally repugnant. The last couple of months have certainly been no picnic. In the final analysis, though, I have been extremely lucky. I have some ability, yes, and that has lifted me to a level above tens, even hundreds of millions of people in the affluent West who will never know the chances I have been given simply by having a natural talent for mathematics. Yet even when I was working 56 back-breaking hours a week shining shoes for tips and could afford none of the trappings of our modern consumer society, when I would have thanked you for a one-hour-less-than-full-time job at a burger joint, I was still much better off than a significant portion of our little world's people. Even when I was homeless and had to stab that fellow to keep him from stealing my blanket and spare socks, I HAD a blanket and spare socks.

Most of the time, I am able to keep that sort of thing in mind. Whenever I've had more than I actually needed to get by, I've given to charities and individuals (a little more than ten percent of my gross income last year, for instance). While I can't fix the world all by myself, I have tried to look after a few of the less-fortunate people in it, both through blind giving to local and international organisations that do more than run trite commercials, and through actively involving myself in the lives of those people I think I can reach. Tikkun ha-olam, the Jews call it, and although I am not Jewish (except, perhaps, philosophically), I have tried to make it the central principle of my life since I regained my sobriety. (Those of you who have a mind to can probably blame Rabbi Michael Lerner for that.)

Sometimes, though, my own relatively petty problems can lead me away from that guiding principle. The past few weeks have been a great example. I was hungry, not starving; I was looking at the possibility of homelessness, but I still lived indoors. And I sincerely thank those among you who have offered support and encouragement -- but at the same time I'd like to tell you all to look elsewhere in the world for people who may not know much about Notes or programming in general, but who need your help far more than I ever have needed or will need it.

The Sumatran earthquake and tsunami has killed, or will kill, something in the neighborhood of a quarter-million people. Along with the initial impact of the waves, there will be as much or more devastation caused by exposure, starvation and disease, and that's if we in the West do all that we can right now, today, to mitigate the catastrophe. As bad that has been, though, we have the exact same things going on in the world every hour of every day. The difference is that this time the causal event took only a few seconds -- that's what made the news. We can all give till it hurts then wander off in a self-congratulatory stupor until the next newsworthy disaster.

Well, folks, there are more people homeless and starving, living in conditions that breed disease and death in this world than were affected by the waves. A very large number of them live only a few miles inland of the disaster, just out of reach of the aid that will be pouring in to the region. A few may be living closer to you -- so close you can't see them, except as a problem. When the tsumnami crisis is over and the nice, touristy coastal areas are rebuilt, please don't forget the rest of the world. Grab what pieces you can, and take them in for repairs. Please. I've seen what the Notes community can do for its own members -- imagine what it can do for the world.

Thursday, December 23, 2004

Remember Me?

And you to whom adversity has dealt the final blow,
With smiling bastards lying to you everywhere you go,
Stand to, and put up all your strength of arm and heart and brain,
And, like the Mary Ellen Carter, rise again!

Rise Again! Rise again,
Though your heart it be broken and your life about to end.
No matter what you've lost,
Be it a home, a love, a friend,
Like the Mary Ellen Carter, rise again!
Stan Rogers (the other one), The Mary Ellen Carter

I have recently and rightly been taken to task for my long absence here. It's not so much that I've given up on the blogging idea; I've just had my mind elsewhere lately. Yes, I've been seemingly active elsewhere on the web, but it only takes me about ten minutes to scan all of the other blogs I read, and throwing a short comment in takes just a few seconds. There have also been the developerWorks postings, but trust me, they don't occupy much of my time either.

I've been busy fighting with DXL, trying to keep my mind off of my non-Notes life. The DXL bit will appear in another posting — it's really pretty cool — but this bit's about the non-Notes life, such as it is.

One thing they don't tell you in the "So you've decided to become homeless..." pamphlet is that it will follow you for the rest of your life. I've been involved in a truly ridiculous situation of late, the upshot of which is that until yesterday I hadn't been paid yet by my new employer. At all. Ever.

On the one hand, I (like most people in the white-collar world) work for an employer who pays exclusively through direct deposit to a bank account. I have such an account, of course — that's how my former employer paid as well. However, the company contracted to do the payroll requires a void cheque in order to begin payment. As luck would have it, my chequebook, which has been otherwise unused lo these many years, was one of those items that are inevitably misplaced in every move. It would seem a simple thing either to get new cheques or, failing that, to open another bank account, right? Not if your name sets off bells and sirens in the credit system, it isn't.

It's not that I have debt owing or anything, I just haven't had any bills to pay that weren't cash on the barrel head for the last dozen or so years, and cash businesses don't make positive reports to credit bureaus. (Or, often, to the revenuers either.) I have essentially been living off-paper for more than a decade without knowing it. Couple that with the anti-laundering measures that have been introduced into the banking system over the same decade or so, and any odd requests come up "potentially criminal". Apparently, requesting cheques from a city in a different province from your home bank branch (which is still in Toronto for the moment) with near-zero balances in all of one's accounts is an "odd request". No cheques for you! Next!

For the benefit of my American readers, I should point out that the banking system in Canada is quite a bit different from the system in the States. Here, the system consists principally of five chartered banks: TD Canada Trust; BMO Bank of Montreal; RBC (The Royal Bank of Canada); The Bank of Nova Scotia (ScotiaBank); and the Canadian Imperial Bank of Commerce (CIBC) along with a few large regional players. All of these institutions are huge (think Citibank), and you can't just go down the street to the Second National Bank of Fourth Street if you don't like the service at the Fourth Street Commercial Bank.

The one constant across all bureaucracies is that policy and procedure take precedence over common sense. I earn a pretty decent salary in modern Canadian terms, am single, debt-free and live in cheap, though comfortable accommodations selected specifically to allow me to save a whole whack of money in the too-few years I have left before I'll have to consider retirement. And here I was, reduced to eating plain white rice for more than two weeks (I knew there was a reason I always buy the feed-an-army-sized bag), looking at January's rent due-date, and seriously wondering what a Montreal winter outdoors might be like. Surreal. And not something I wanted to spend a lot of time dwelling upon.

Almost everyone has had some sort of financial fright at some point in their life. Going through the big one a second time gives me, I think, the right to assert that the fear of the unknown doesn't begin to compare to the fear of the known. Yes, I was scared. And angry. I've worked my buttocks off (figuratively — I have actually accumulated surplus pew-padding in the physical sense) to move from homeless disposable humanity through skilled shoe-shine boy to relatively well-respected Notes developer, and it all seemed to be coming to naught in a huge hurry and for no good reason. I tried to write about it, but the level of venom in what I was writing was beyond the pale. (A pale, by the way, is one of those sharpened-log, overgrown picket fence deals you see around forts in Western movies, or, rather, the mediaeval version of it. That which was beyond the pale was out of the Lord's protection, lawless and wild.) Angry as I was, even I could see that my anger had displaced reason completely. The recent little incident (hereinafter referred to as The Incident) in the Notes blogging community wouldn't have been noticed at all had I published, and I would have committed social and professional suicide.

It seems that some sort of resolution has been reached, though. And my bank was kind enough to let me take 25% of my earnings — I still have to wait until January 4 for "clearance" (on a certified corporate cheque?). It's a bit late for Christmas, but the new year should be a happier one. Perhaps with that load lifted, you may see a bit more output here.

If only I had listened to the other Stan.

Speaking of the other Stan....

A number of bloggers, including Volker, Ned and Rocky have recently mentioned Google's new Suggestion service. I tried my name, just to see what would come up. Although I saw myself cited and published in a whole bunch of places I wasn't aware of (mostly repostings and translations of comments I've made in the developerWorks fora by people who've mistaken me for an expert, several in commercial newsletters and not-so-free Domino advice sites that really ought to pay for what they take — or at least ask permission), the search was much as I expected. There are just under a hundred thousand results pointing to this Stan Rogers either directly or obliquely; the remaining 1.2 million or so refer to a folk singer who died tragically and far too young in an airplane fire at Chicago's O'Hare airport in 1983.

I've been listening to Stan quite a bit lately. Not for any particular reason; I'm just in a period where homey, honest music is what suits my tastes best. There's a bit of being at home in folk music, at least for me. I come from a family that sang the old songs around the kitchen table whenever there was anyone over for company. I don't know how well the "kitchen party" translates into your culture(s), but it is, or rather, was, a way of life in a large part of Canada. It's probably a Celtic thing, since it applies to the Normans and Bretons of Northern Ontario, Quebec and l'Acadie as much as it does to the Scots of Nova Scotia, the Irish of Newfoundland and the Welsh, Cornish and Geordie of any mining town. It's sad to see all of that being wiped away by cable TV, the internet and Nintendo. Not to mention the modern insistance that the kitchen and dining room be separate places in the house.

(When I buy a house, the first thing I plan to do is renovate to make the kitchen the communal area it ought to be. I don't care what it does to the property value; there are other values more important in life. The chef in me still wants a kitchen full of toys, but if it doesn't have a big ol' table, enough chairs and rickety old stools to seat half the town and direct access from the back door, it ain't a proper kitchen. Oh, and the company that comes in the front door is not the same bunch as the friends who come in the back. That may be small-town of me, but that's the way I've always felt. Where I grew up, salesmen, strangers, and maybe the boss came through the front door. The people you wanted to see knew that the welcome mat and the kitchen were around back.)

I really can't imagine a life without music. Not as background radiation, but as something that I actively listen to and participate in. Nor can I imagine knowing a little of what life was like for people in times gone by without having their songs to tell me who they were, what they did, and what they felt. You can't know the old colliery if you can't feel the grit on your skin or the black in your lungs; you'll never know the old fishing grounds if all you know are pleasure craft and modern trawlers. Somehow, the old songs make me feel like I've had a taste of the real thing. Stan was only twelve years older than me, and a young 34 when he was taken from us, but there was something undefinably ancient in his words. It was as though he had lived through the times he sang about, the days of wind and sail. Even his modern "moment in a life" songs had an air of poignant reflection you wouldn't expect to find in someone so young.

I've actually met people who don't sing. Ever. I know young people who don't know a single non-national-anthem song all the way through. There's something horribly wrong with that. You would think, no matter what your opinion of modern popular music (whenever modern happens to be), that there must be at least one song that resonates with every one of us. A lyric with special meaning, or a melody that pulls at the heart in just the right way. Something.

I freely admit to being a child of the sixties. I lived through the height of folk music's popularity, and I heard a lot of music that went out of its way to be meaningful. (Some of that meaning was so forced as to be cheesy, but at least people were trying.) So did a lot of people who see music as background noise to keep the quiet at bay. It wasn't what was on the radio that made the difference for me, it was what was in the kitchen.

By the way, if we ever get together, you and I, feel free to ask me to sing a few of Stan's songs. For many of you, it's the only way you'd ever be able to tell your friends that you actually heard Stan Rogers himself sing "Barrett's Privateers" or "The Mary Ellen Carter" live — maybe even in your kitchen.

The Devil is in the details

I absolutely hate about fifty percent of my job. Don't get me wrong, I wouldn't want to do anything else at this point in life, but there's a big part of the job that gets on my nerves. Give me a non-trivial problem to solve, and I can come up with a solution that would knock a lot of socks off. Tell me what an application has to do, and I can come up with what is usually a decent, often best-case architecture in next to no time, and get from there to solid code in a flash.

Just don't ask me to create a compelling UI, at least not for the Notes client. No, let's make that anywhere -- the few really nice web UIs I've ever designed were probably accidents. I shouldn't be trusted with fonts, tables and colours on a computer.

I'm not artistically challenged. Give me a canvas, oils and brushes and I'll paint you something you'd want to hang. Hell, I'll paint you something you'd want to buy. Give me some fine parchment or the rags to make some real paper, a collection of cured quills and reeds, a knife and some inks and I can show you what calligraphy can be. I just can't translate any of that to the computer. I can't do it in an illustration or paint package, and I certainly can't do it with declarative statements, at least not without far too many hours of trial and error. Now, if you can show me what it's supposed to look like, spec the colours, etc., I can get from your picture to a working version quickly and painlessly. That's mechanical. It's the creative aspect I can't do if there's a machine between me and the work.

I would love to find myself in a place where I could just throw the UI to a designer. I've heard that HTML support in Notes 7 is supposed to be a huge improvement over what's in Notes 6 (that's apparently a last-minute decision, if I read people like Debbie Branco correctly, so it probably won't be true in current betas). I wonder if it will have progressed to the point that a simple code monkey like me can delegate the artsy bits to people who are actually good at that sort of thing.

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.

<PopularCultureReference>
<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...
</PopularCultureReference>

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;
@Command([ViewRefreshFields])

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;
Command([ViewRefreshFields])

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;
@Command([ViewRefreshFields])

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;
@Command([ViewRefreshFields]);
FIELD SortSelection : = NewValue;
FIELD NewValue : = "";
@True

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 OpenNTF.org (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 OpenNTF.org'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 OpenNTF.org'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.

Wednesday, October 27, 2004

Quote of the day

"A political candidate who jumps to conclusions without knowing the facts is not the person you want as your Commander-in-Chief."

George W. Bush, 27 October, 2004

Need comments? Didn't think so.

Monday, October 25, 2004

Being (busy) and Nothingness

Sorry to leave you stranded, folks. Someone seems to have excised about two hours from the average day. I figure it's just a new tax -- I really should have paid more attention to the Speech from the Throne when Parliament opened.

The real time has gone into documenting weird forms. I'm not sure why the code got as weird as it did; maybe somebody out there can clue me in. Still, the app works now, and I have to make sure that nobody performs a weirdectomy to make the app look like a normal Domino app.

What I've done would only be considered a WTF in our little Domino world. I was putting a web app together, and several of the fields in this app are hidden computed fields that get their values via actions and dialogs. Taking that app to the web, I concocted a little under three thousand lines of selectively-loaded JavaScript. (The <script> tags are computed at run-time, the code itself is cacheable, and only a handful of "Gods of the database" will have to download more than a small subset of the JS. Did I mention that the app was non-trivial?) Well, a funny thing happened on the way to the Forum -- I couldn't get the hidden fields to work. It didn't matter whether I left them visible and used the HTML Attributes object or hid them and used "Generate HTML for all fields" the data would always come up truncated in the HTML source. I ended up having to hand-code the HTML fields in passthru with the Designer fields between quotes in the value attribute. I'm not talking about a couple of fields, either. This may be normal in ASP, PHP, JSP and so on, but dammit, this is Domino. I shouldn't have to wear out my typing fingers like that. (I have other fingers that are never used for typing, and they're still relaxed and comfortable. Thanks for asking.)

Anyway, I hope to be firmly into "what are they gonna do, fire me?" mode for the rest of the week, so I should be a bit more active here. See you tomorrow.

Thursday, October 14, 2004

Upping the Heaval Factor

Sorry if I've been absent for a while. If you think this blog's been missing beats, you ought to see me in real life these days.

Those who have been following the story so far have no doubt picked up on the fact that there hasn't always been a great deal of stability in my life. I have had the same job for four years now, and it has been rather comforting to have a regular income and a roof over my head. The fact that my work environment was keeping me less than happy was immaterial, as was the fact that the roof currently over my head is the ceiling of a ten-foot-square room in a transient hotel (the bathroom's down the hall). Examining that objectively, you'd probably come to the conclusion that my lifestyle is, well, crap. And you'd be right. But to someone who knows what it's like to sleep in the park without calling it camping, it's a hell of a lot easier to hang on to a bad certainty than it is to take a chance trying to find something better.

Well, events have been conspiring against me lately. The company I work for as I type has made its long-term plan for Notes clear, and that future is a Notes-free environment with no plans to build an equivalent collaborative environment on any other platform. Those of us with half a clue have put the migration off as long as we could, even momentarily expanding the Notes footprint, but there's no stopping C-levels with tunnel vision. To tell the truth, the move to Exchange would be a huge step up from the previous corporate solution, but it ain't Notes by a long shot. In fact, the possibility that we might obtain the tools to create a collaboration and workflow environment using anything beyond what comes free in the box with Win2K3 and Exchange has been clearly eliminated from consideration recently. I have no desire to become an email administrator, and that's not just because I resent the move to Exchange. I'm a puzzler, a problem-solver, a developer, and being reduced to helping fat-fingered users find errant attachments and resetting passwords after every long weekend is not what I'm built for.

Next, after fourteen years of searching, I have finally been able to track down someone who was once the most important person in my life. When I knew her, she was just a kid who'd been forced to get by on her own at far too young an age. With all the troubles in her life, though, she was perhaps the brightest and most articulate person I've ever known (in person at least, dear Readers), and a beautiful soul to boot. All she was missing, really, was a good dose of self-worth, and I tried to help her find that. You know, I was a really nice guy back then -- it was after I'd been sober for long enough to become (and I hate to sound hokey here) spiritually centred, and before I was affected by the ravages of homelessness. I wish I had been able to be there for her for a longer time, but the military doesn't just let you live wherever you want. I may introduce her to you later (she's notable in her own right for the work she's done) but for now it's enough to tell you that I'm hoping that regaining that connection to the person I once was can bring some healing to my life.

She's living and working in Montréal now, and that brings us to Exceptional Event Number Three: an innocuous posting in each of the developerWorks Notes and Domino fora by Ben Dubuc announcing that there were a couple of dev positions available in Montréal. That was one too many coincidences for me to ignore. I enquired, and was more than satisfied with the enthusiasm in the shop. For my favourite platform, I mean. The fact that my name seems to have set off bells of recognition in the Notes group is a little less thrilling. I know I'm good, or, rather, I like to think that I know at some intellectual level that I'm better than bad, but something inside me keeps me from believing that I'm good enough. It's probably a hangover from the days when I was being treated like human refuse. I hope that the expectations aren't too high, 'cause I'm taking that job. And the laws and prices in Québec are such that I will be in a proper dwelling this go-round.

I tendered my resignation from CGI Group Inc. a few minutes ago, and I'm off to Montréal and Computer Horizons for November 8. In the interim, folks, any of you who have my cgi.com address in their addy books should change it to stan dot rogers at gmail dot com for now, particularly if you aren't interested in receiving NDRs. I'm running headlong into the unknown with a mixture of excitement and apprehension. My head and my heart tell me that this will be a fantastic voyage. My demons tell me that everyone will find out I've been faking all this "expertise" stuff and that I'm going to crash and burn. My stomach tells me I have to go throw up now.

Monday, October 04, 2004

Troubles Posting

Had a wee problem with Blogger this morning, which resulted in a whole hockey-sock full of duplicates of the previous entry. Management apologizes for any inconvenience, but will not accept any liability for mental distess.

The world will end at midnight....

... and I know this because I was watching "This is America with Dennis Wholey" on PBS, and there he was, Pat Buchanan in all his glory -- but he was making sense. To me. Pat Buchanan has never made any sense to me. I haven't changed my opinions, so this must mean that there's been a fundamental shift in the Universe somewhere, and that the end is nigh.

Wednesday, September 29, 2004

Office Pools As Learning Tools

As we approach the United Way silly season, I can't help thinking that most of the really useful Domino web tricks I've come up with were not so much aimed at solving business problems, but at solving various charitable fundraising problems. Eventually, all of these methods made their way into one line-of-business support application or another, but the fact remains that the tricks came about because I needed new and compelling ways to separate people from their money. And the best part of doing it on Domino is that authenticated users can't deny participating when the collector comes around to their desks.

Games and pools need to be immediately usable. If the user has to ask, they will move on without actually asking. Most of these deals run on a fifty-fifty basis (the winners can claim half of the funds taken in, the charity gets the other half, and the administrators and developers can bask in the glow of a good deed well done), and a user can tell at a glance whether or not the potential winnings are worth the price of admission. After all, if you're the only one dumb enough to play, then all you can get is half of your money back. You might as well have just given the money to charity to begin with — and the reluctance to do that is why we're building the games in the first place.

What follows is a necessary disclaimer designed to cover my posterior like a pair of corduroy Dockers: I am not suggesting, even for a moment, that anyone develop or deploy any lottery or game of chance not fully licensed and approved by the appropriate federal, provincial, state and/or municipal authorities. We now return to our regularly-scheduled broadcast.

Take a look at any of the duly-authorized and completely above-board draws, bingos, auctions and/or pools that may be run in your organisation today, and think about how you might move them to the technological cutting edge using nothing more than your wits and your favorite collaboration platform. You'll probably find, as I did, that truly compelling game UIs are web creatures. It's not that you can't create a compelling UI in the Notes client; but the business-oriented Notes client isn't very good at the kind of Vegas-inspired UI effects needed to strip the very food from your co-workers' children's dinner plates. For charity, of course. If you do it right, though, you can bring some of what you've done back into your business apps and make it permanent.

Tuesday, September 28, 2004

Still Kicking ...

... but somewhat busy. We will rejoin our regularly-scheduled programming shortly. Please stand by.

Being Publicly Private

Warning: Morality Tale

As Ed has noted, in the first two weeks of blogging I seem to have exposed quite a bit of myself. I've done that for a couple of reasons, and I hope to explain them here.

I really believe that's it's hard to know someone without knowing where they came from. Obviously, I am going to hold some opinions that may be hard for the normies out there to understand. I will react to some things in quite a different way from folks who haven't been where I've been. I have made some eFriends (a couple-three-four of whom I believe I could just call friends if all of this electronic nonsense were removed), and those people should know why I believe the things I believe, and why I react the way I do.

Apart from that, though, I want to put aside the idea that things like recovery from addiction and homelessness are things that people should be ashamed of or afraid of talking about. I was a horrible drunk — more than nineteen years ago. I am sure that my friends and family didn't speak of it proudly. My co-workers and supervisors probably didn't brag about having the highest-flying, least-reliable SOB who ever lived on their crew (and YOU can't have him, nah nah nah nah nah nah). When I entered recovery, I sure didn't want anyone knowing who I was. Oddly, I had no problem demonstrating addictive and impaired behaviour in public; it was sobriety that was a problem, I guess.

I am an alcoholic and a multiple-drug addict. I will be that until the day I die. I am not ashamed of my condition, any more than I would be ashamed of having leukemia that was in long-term remission. No-one can hold my deep, dark secret against me unless I try to keep it secret.

At least the first few steps on the road to addiction were voluntary. I can see how some people might be inclined to attach a moral turpitude tag to that aspect of my past. People who don't drink can never be alcoholic; those who don't do drugs will never become addicted to them. (I exclude here those who have become addicted to narcotics due to poor medical practice.) What I can't wrap my tiny little mind around is how people can attach the same attitude to something like poverty; yet I can say that I was treated with more scorn and disdain as a sober homeless man who spent every penny he could scrape up at Kinko's printing resumes than I was as a raging, but employed, everything-oholic.

Something about that strikes me as wrong. Yes, we're all aware of the folks who are left on the streets because of ridiculous libertarian attitudes towards people with severe mental illness, and there is that segment of the population, particularly among youth, who ain't gonna kowtow to The Man no matter what the personal consequences may be — at least until they've had a proper chance to evaluate those consequences. There are those, though, who have just had a string of bad luck.

There are a few of you out there, I suspect, who believe (as I do) that I have at least a budding aptitude for this line of work, yet I spent nearly five years shining shoes (and losing tech currency with each passing day) before I was able to land a zero-expectations, probationary, entry-level job. I'm not the only useful person who has ever been dealt a few bad blows in life — I met few folks on my journey who should have been doing something more than looking for their next meal. What this all boils down to is a plea that you try to judge people on the basis of who they are as individuals. You never know who you might find if you can look past the shabby clothes for a second or two.

Wednesday, September 22, 2004

We Know Why You Fly

Does anyone else out there find the new American Airlines commercial a little, um, disturbing? Data mining might get them when, where, how often and with whom, but how are they getting why? Time for the tin foil hat, I guess.

History Bites

I don't know if this Canadian television show is available outside of Canada, but if you get a chance to check it out, you should. You may me able to find it on your local public broadcaster or on the non-Canadian version of the History Channel.

The show is the brainchild of Rick Green, whom Americans might know as the hapless title character of the "Adventures with Bill" segments of The Red Green Show. Green is actually a capable and accredited educator, but he's been involved in ensemble comedy in Canada for about as long as I can remember. A few years ago, he took advantage of his "star" power to propose a project that would combine the two.

History Bites is premised on one question: "What if television had been there?" Each half-hour show has the viewer flipping through the channels on his or her television, offering glimpses of critical events in the past from news clips on CNN (or whatever it might have been called in that region at the time), interviews with key figures by Larry King and Barbara Walters, period lifestyle clues from episodes of Seinfeld or All in the Family as well as "science" programming, kids' shows, David Letterman, Dennis Miller, the cable company's TV Guide channel, and so on. The ensemble cast, featuring Ron Pardo's incredible mimicry, does a creditable job of capturing the flavour of the shows they parody. As with most educational programming, there is evidence that "no bank balances were harmed in the making of this motion picture", but this is educational programming that doesn't feel especially educational. You (and your kids) will love it, even if you don't always get the local Toronto commercial references. Don't worry, the history is not all (or even much) Canadian, just the local commercials.

If you know anyone who doesn't like reading about a bunch of dead white guys (and, let's face it, most history text books are drier than the begats in the Torah), treat them to this. It might spark enough interest to make them want to learn more.

Tuesday, September 21, 2004

Vroon! Weeee-laaah!

A few days back, I was the victim of an attempted drive-by trolling. The young fellow who posted the comment had put on his best Zaphod Beeblebrox persona in what I assume was an attempt to impress with sheer hoopiness. Oh, the irony!

Your Humble Narrator has been clean and sober since July 10, 1985. In the six or seven years prior to that date, I would have made old Zaphod look like Arthur Dent on the wagon. I have imbibed, inhaled and ingested (but never injected — I hate needles) more than any human can reasonably expect to survive; in fact, without frequent intervention, I would not have lived through the period. There were a lot of days when I had at least two heads, and they both (or all) hurt like hell. And I have experienced the sudden appearance of a molten landscape with penguins without benefit of an Infinite Improbability Drive. Yeah, I really knew where my towel was.

I was a two-four-a-day maintenance drinker. (That's 24 bottles of proper 5% Canadian beer, Newkie Brown, Guinness and/or various high-test, 14%+ homebrews for those whose experience is with three-two Bud.) There were times, though, when I needed to get drunk, so I might throw a forty of Navy rum (or, when I needed to feel high-class, a bottle of Chivas Regal Royal Salute or maybe an eighteen-year-old Islay — not that I could tell any of it from peat moss and rubbing alcohol in the state I was usually in) onto the fire. If I could find that Ol' Janx Spirit, I'd have guzzled gallons and damn the side effects. I downed eight successive 48-oz pitchers of draught in twenty minutes to win a beer-drinking contest once, after having gotten enough of a buzz on to rise to the challenge (one of the few things I actually recall is that my "crew" was already at the point of singing the three or four lines were knew of The Black Velvet Band over and over again when the temporary-duty-trip grunts challenged us Noble Aircraftsmen to a match of military skills). I had to rupture my abdominal wall to accomodate the volume, but this was for pride of service. I was quite adept at getting scrips for whatever uppers or downers I wanted (Seconal was a particular favorite), and you just know there are times a fellow needs to, um, get mellow. One also needs an occasional face-to-face with the deity of one's choice, and let's not forget about the poppy juice — everyone needs a break now and then, and it really helped with the never-quite-healed-properly-broken-neck pain. My favorite game was Morning Jeopardy!, and the correct questions were always "what the hell time is it, Alex", "where the hell am I, Alex", and "who the hell is this Alex you think you're talking to?"

The surprising part of all this is not that someone can voluntarily do that much damage to his body and his psyche, it's that he can do the vast majority of that damage while in the military, maintaining critical avionics systems that, if things go wrong, can try to force a Sea King helicopter to maintain a hover precisely forty feet underwater. When I was on my game, you see (that is, when I wasn't in a falling-down stupor), I was a hell of a tech. People were not afraid to express their disapproval to my face, but they always covered my ass. Nothing ever hit paper and stuck. At the time, I thought that was a Good Thing. I may have had to do extra duties now and again, but I stayed out of jail and (this is the important part) I was never administratively referred to rehab. Once you get referred, you have to stay dry for a year or face discharge.

Then I woke up one morning completely blind. I'd been "dead" before and revived; that wasn't a problem. This time, though, I might have to live with the consequences, in the dark, and that was scary. After what seemed like days, but was probably only a couple of hours, the light came back, and I wished it hadn't. I'd never felt that level of pain before (not even from a 1981-vintage cranial arteriogram; ask anyone who's had one what that's like). It took me more than two hours to button up my uniform shirt (timed it; hell, I was already beyond late and was trying to compute the AWOL punishment against the out-of-uniform penalty, and shaving was out of the question that day since my hands were doing pretty much what they pleased without consulting me). I managed to half-stagger, half-crawl to the hangar. Even looking and smelling like I did, and after arriving several hours late for work, it took an unbelievable amount of time and interviews with superiors to finally get my request for a voluntary medical referral to an alcohol rehab clinic approved. Losing my diagnostic skills for twenty-eight days and an afternoon a week for a year was, apparently, worse than watching me kill myself that way.

That was July 10, 1985. Nineteen years and a bit later, I still live with the damage I did. My heart and liver are largely scar tissue. The neck I broke playing Rugby drunk still causes me pain and occasional partial paralysis of my left side. (Five-ten and fifteen stone is small for a tighthead prop, even if you can push a ruddy ton uphill. Sober people that size play the wing three-quarter or, if they're smart as well as sober, sit singing bawdy songs in the stands while the monsters on the pitch get on with the carnage.) The back I injured falling (drunk, of course) from a Sea King flares up now and again, and needs traction for relief. I am prone to paranoid depression, and worst of all I can't find the little travel bag with the tin of olive oil in it. When I'm not stepping out of the shower, I really don't give a flying [censored] where my towel is anymore. I've dealt with much scarier things than the Ravenous Bugblatter Beast of Traal and lived to tell the tale.

To sum up: if you want to try to impress me with your drinking prowess or tales of chemical adventures, you can't. Whatever you've done, I've done more and couldn't be less proud of it. And if you're covering the ass of someone who has a problem like mine, the little trouble they may get into now is nothing compared to the big trouble that's coming. Stop it.

Why code here?

A big part of what I wanted to do with this site from the beginning is captured in the web calendar articles. It's not about putting code out there so much as allowing you to follow my thought processes as I put some of my ideas together. I am not a particularly brilliant coder, although I do score the occasional three-pointer. Heck, I'm just now getting to a level that lets me see the humour on The Daily WTF. If I have one indispensable quality, it's that I refuse to be locked into the obvious. I am willing to explore a few of my wilder ideas, even if some of them lead to dead ends. They're not all winners. Not by a long shot. With the calendar, I went through a number of decreasingly-poor implementations before polishing this version publicly. I would not have used the calendar here at all except for the recent rash of requests I've received, precisely because it was pretty much a done deal already.

I'm not trying to create any how-tos. There are a lot of people who would be better hosts of "This Old Database" (or is that "The New Yankee NSF"?). I want to try to explore the creative process. If you were to ask me which of the calendar articles I thought was the most important, it would have to be The Principles (Part I). As I said, any idjit can code the thing once he (or she) knows where he (or she) is going. It may take some longer than others to put the code together, but all it takes is a bit of Formula Language, a bit of basic HTML, and an even littler bit of JavaScript and CSS to make it work. None of this really requires grokking Notes or the web; the "aha!" for me was simply realizing that when a view is set to "Treat view contents as HTML", the actual output from the view didn't necessarily have to be HTML.

The hard part of a development project is rarely the actual coding (assuming a basic knowledge of the language and environment), it's in evaluating the starting point, determining the destination, and developing the roadmap between those points. The rest is really just filling in the blanks. Yes, there are good ways and bad ways to fill in those blanks, but in the end it's a mechanical process. When the destination doesn't appear to be something that came on the CD, you need to let your imagination loose. (Outline views won't create displays for dates that don't have an entry, and Calendar views don't let you categorize. Now what, smart guy?)

The next time you see code here, I hope that it is a fresh problem for me as well as for you, and that it is once again something that Notes "doesn't do".

Sunday, September 19, 2004

Lazy Nogoodniks may now download That Damned Calendar

The code has been uploaded to the OpenNTF.org CodeBin as "Open WebCalendar". It's an .ns5 to make things easier all-round; ND6 users can make the few changes required to make it an all-6 solution if they want. In keeping with the spirit of OpenNTF.org, please repost any improvements you make to the code. Since this is not a fully-realised application, it has gone into the CodeBin instead of being thrown out as a Project. I will periodically review the entry and make any fixes/improvements as may be necessary.

Friday, September 17, 2004

That Damned Calendar: Errata

While preparing the database for OpenNTF.org, I ran across a couple of errors in the code and a couple of things that I could have done a bit better. I have no qualms about editing postings like those ("articles" as opposed to "opinion and observation"), so the original articles will be corrected as necessary. Those who have been following this since the beginning, though, will need to be told which bits have been changed and why. Let's do the bugs first, shall we?

First, the goToDate() funtion is missing a set of parentheses. As it stood, if there were no categories in use, you would always go to the redirect page, and then to the current month. The problem is this line:

window.location.search = 'OpenView&RestrictToCategory=' + (cat=='')?'':(cat + '~') + year + '-' + month;

If "cat" is an empty string, then year and month are ignored as well. The line should have read:

window.location.search = 'OpenView&RestrictToCategory=' + ((cat=='')?'':(cat + '~')) + year + '-' + month;

As well, the line checking the Category field is going to cause fires and explosions if there are no categories in use in your application. The Categories field does not exist, and when you try to examine its properties, the script errors out. This line:

var cat = f.Category.options[f.Category.selectedIndex].value;

needs to be changed to:

var cat = (f.Category)? f.Category.options[f.Category.selectedIndex].value: '';

Next, the formulas for the Previous and next links are missing "?OpenView&RestrictToCategory=". The formula given for the Previous link should have read:

lastMonth := @Adjust(StartDate;0;-1;0;0;0;0);
year := @Year(lastMonth);
month := @Month(lastMonth);
textMonth := @Select(month; "January"; "February"; "March"; "April"; "May"; "June"; "July"; "August"; "September"; "October"; "November"; "December");
"<a href=\"" + @ViewTitle + "?OpenView&RestrictToCategory=" + @If(SelectedCategory = "";"";@URLEncode("Domino";SelectedCategory) + "~") + @Right("000" + @Text(year);4) + "-" + @Right("0" + @Text(month);2) + "\">" + textMonth + " " + @Text(year) + "</a>"

Note, too, that I have put "@ViewTitle" in where ViewName used to be. I don't know quite what I was thinking when I typed the original posting; the application has always used @ViewTitle, even in its earliest incarnation. Sorry. And I have incorporated the enhancement discussed below.

On to the refinements, then. I had been storing a URL-encoded version of the current category in the SelectedCategory field. It occurred to me that it's easier to @URLEncode that value where it needs to be encoded than it is to try to decode it when needed for R5 use, like for the window title and page heading displays. Change the formula for the SelectedCategory field to:

@Left(Category; "~")

The formula for the category picker already has taken this change into account. Again, there may be typos remaining. The demo database is clean and works, but getting its bits and pieces up here as HTML has induced the odd problem. If you find anything, please let me know.

Addendum

A change to keep Internet Explorer happy: in the two spacer fields, insert a &nbsp;into both the "spacetop" and "spacebootom" cells. That will force the borders (if any) to display in those cells.

For Mozilla: add "height: 58px;" to the DIV.cellcontent CSS entry, or Mozilla will extend the <div> content outside of the table cell.

Thursday, September 16, 2004

The band will be rockin' when I get there

There aren't many Ramones left on this terrestrial plane. Johnny has joined Joey and Dee Dee on stage at the great CBGB in the sky. Unconfirmed reports have St. Peter greeting him at the gates with, "Gabba, gabba, we accept you, one of us."

Johnny Ramone
Rockuiescat in pacem

About that hockey lockout....

Fans of the current game should probably avert their gaze. I don't hold opinions, I speak eternal truths, and sometimes the truth hurts. You are free to disagree with me, of course, and I will defend to the death your right to disagree and voice that disagreement — but you'll have to do so with the certain knowledge that you will be wrong.

The mere fact of my Canadian birth and life-long residence in my Home and Native Land makes me eminently qualified to comment authoritatively and in depth on the state of the game of professional hockey. It is my stereotype, my heritage and my birthright. And now, with news of a lockout of the NHL players eclipsing coverage of events of genuine social, political and economic import throughout the Canadian media, it is my wont.

The current NHL game blows buffalo-bladder bagpipes. Badly.

I am just old enough to remember the Original Six, but even the Gump had given in and started wearing a mask in net by the time hockey began to rule my life. (That Age of Ascension was once an inevitability for Canadian lads.) Even though there were very few players who were not Canadian-born playing in the NHL, at eighteen suited players per team and only six teams, you can be sure that the players who made it to the NHL were better than just good. There were guys that could handle themselves in a confrontation (even Bobby Orr, the Gretsky of his day, could go ten rounds with the baddest cat on the other team), but there was no Designated Goon (the player whose only purpose is to cause injury to an opposition target). Helmets? Unthinkable, unless (like Stan Makita) the player had suffered an injury of such a kind as to make continued play without a helmet suicidal. No, children, there were no armoured players in those days. The pads that were worn were made of stiffened leather over a thin layer of felt. The game was chippy, but because the guy doing the chipping was as likely to get hurt as the guy being chipped, there was nothing like the level of violence you see today. Yes, there were abominations, such as the Two Handed Clubbing, of which you can read in the Saga of Maurice the Rocket, but I'm talking about the day-to-day style of play.

At the same time, the team/player relationship was such that a player would likely be with a team for his entire career. More often than not, that player had spent his entire junior career in the team's farm system. Sure, some of them may have had to work at normal jobs in the off-season; after all, free agency had not been invented yet. I'm not saying that professional players should be making the equivalent of eight bucks an hour in this day and age. The old, bad system meant that fans knew their players (hereinafter referred to as The Good Guys), and could hold some real enmity for the players on the other teams (Them Bums). The change to free agency and the player movement that it brings has pretty much killed the Habs. Les Canadiens were not just a team in Montréal, they were the team of Montréal and of all of Québec. My team was the Leafs, and by Leafs I mean Davey Keon, Tim Horton, Norm Ullman, Ronny Ellis, Johnny Bower, et al. None of them was picked up on a short-term contract for a playoff run.

Then came The Expansion. Twelve teams. The game seemed to survive it. At least I found it not only watchable, but exciting. A few years later, Buffalo and Vancouver were allowed in to bring the total to fourteen teams, and the roster size was increased. At about the same time, I began to lose interest in the game. I still watched it, but I didn't enjoy it nearly as much. Given my age at the time, I put a lot of that disinterest down to the inability to easily collect a full-league deck of hockey cards and memorize everyone's stats.

The horrible truth was that the game was changing, and not for the better. I finally realised that there was a problem during the historic Canada/Russia series in '72. It wasn't just that the Canadian team looked out of shape and sluggish compared to the Russians, it was that Bobby Clarke had to deliberately cripple Valeri Kharlamov for life in order for the Canadian team to even appear competitive. I could not celebrate the Holy Goal of St. Paul of Henderson; I was in the can puking.

I demanded emancipation from hockey's oppression the day I saw Dave Schultz of the Philadelphia Flyers in a playoff game skating with one leg actually thrown over the back of an opponent, as if to ride him like a pony. Something about that just told me that this wasn't My Game anymore. The Howie Meeker game was gone, the Don Cherry game had come to take its place.

I have watched the odd game since then, and I can honestly say that I have seen nothing to improve relations between me and The Game. Checking is no longer about puck control, it's all about inflicting injury. More armour equals more injuries. Hearing rabid Leafs fans cheer as Brian McCabe makes yet another deliberate and obvious dive at an opponent's knees arouses such a depth of anger in me as to endanger innocents around me. This may be what they want to see in Phoenix and San Jose, but it ain't Hockey, and I ain't a fan.

So the NHL team owners have spent themselves into a hole, and now they want to take a mulligan on those player contracts. As much as I am sickened by the idea that some kid who can hardly spell his own name (because he was playing major junior when he should have been going to high school) can make seven million U.S. a year doing something that, in the final analysis, makes absolutely no contribution to the betterment of mankind, I am nauseated more thoroughly by people who, in the course of operating their businesses, have wilfully and knowingly spent more than they can reasonably hope to take in, and then expect other people to pay the price for their mistakes. Neither side is particularly right in this dispute; the owners seem more wrong, but I'll have to go with "C: Let's start all over again", Regis. Final answer.

That Damned Calendar: The Rest of the Story (Part IV)

So far, we've set up a calendar table that can represent any month of any year from January, AD1 to December, AD9999. (By the way, Notes and Domino is not Y10K-compliant, so there should be developer and admin jobs available for at least the next eight thousand years, with an expected increase around fiscal 9997-9998.) The calendar will respond and adjust to URLs, and we've provided user navigation. Lovely, if all we needed was the ability to print blank calendars. If that's all you need, you can quit coding now and work on the CSS — you're done. The rest of us have to display entries in the calendar.

Ultimately, we need to create view output that will call the addEntry() function on the $$ViewTemplate. If you have been following along from the time this series was originally posted, you need to know that I have changed the addEntry function on the $$ViewTemplate to make the view output smaller. The function should now look like this:

function addEntry(cellId,displayText,urlLocation,titleText) {
var cellArray = cellId.split(';');
for (i=0; i<cellArray.length;i++) {
try {
var cell = document.getElementById(cellArray[i]);
var newLink = document.createElement('a');
newLink.setAttribute('href',urlLocation);
newLink.setAttribute('title',titleText);
var newText = document.createTextNode(displayText);
newLink.appendChild(newText);
cell.appendChild(newlink);
}
catch(e){
error += e.message;
}
}
}

The change was made so that entries that span multiple days only need to occupy a single row in the view. (I did warn you that this was a work in progress, right? This doesn't represent a "fix" as such — call it a refinement.)

Given the function, we would like a event entry covering October 1st and 2nd of 2004, titled "2 Day Soiree", and running 8:00 to midnight each day at the Community Centre to make this call:

addEntry('2004_10_01;2004_10_02','2 Day Soiree','EventCalendar/B40F7437715FC15F85256ED30041D17A','8:00 PM - 12:00 -- Community Centre -- Public Welcome');

Of course, that would just be the setup of this particular hypothetical Events database. The title attribute of a link is a valuable asset to an application like this one, since the individual calendar table cells are by their nature too small to include much information in the link text. Don't be afraid to load the title to the gunwhales.

There's not a lot to creating the output, but there is some calculatin' to do. If at all possible, it would be far better to do the caculations on the calendar entry form, but if you're adding the functionality to, say, a Lotus template or some third-party design, you'll need to do the cyphering in the view. It's no big deal to mark a single view and the $$ViewTemplate as "Prohibit design refresh or replace to modify", so you can use this view and still take advantage of template upgrades. If it's a custom design, then do your server a favour and do the calculations on the form. In any case, make sure the view is set to "Treat view content as HTML"; this is code, after all, and we need literal character, not HTML tables or Java applets.

The first thing that needs to be computed is a date list that runs from the start date to the end date. If your design only allows for single-day entries, you can skip this step. (A heads-up here: if you are working exclusively with single-day entries, you can put a hidden sort column in the view, sorted by start time, to force multiple entries on the same day to appear in the correct order. That's not an option for possibly-overlapping multi-day entries, at least not yet. I haven't wrapped my head around the JavaScript to do that bit yet, although I am getting closer.) The formula for that is:

@TextToTime(@Text(@Explode(@TextToTime("[" + @Text(StartDate) + "-" + @Text(EndDate) + "]"))))

That's an R5 formula, I know. For ND6, replace the @TextToTime with @ToTime, and eliminate the @Text around the @Explode. Somewhere in the R5 code stream, the behaviour for exploding a date range changed on me. Don't you just love it when an admin upgrades the server without telling you and you come to work to find eleventy-nine hundred trouble tickets in your inbox?) Since I had seen the results returned as a date list in some versions and a text list of date-like strings in others, I had to use @Text followed by @TextToTime in order to ensure that the final result was a genuine date list. If you do the computation on the form, call the computed multi-value date field IncludedDates. If you have to do it in the view, make this column the first column in the view, hide the column, make sure it is not sorted, and give the column the programmatic name IncludedDates. (Since this value needs to be available to calculate both the category and the cell ids, it has to be the first column. If you were to sort the column, it would interfere with the RestrictToCategory call.)

The next thing to compute is the view's category value. Again, you can do the computation on the form and just use the field name in the view, or do the calculation in the view itself. In either case, we need to derive the year and month values from the dates to get them into the format "yyyy-mm". Both the four-digit year and the two-digit month are required; if you were using the view to display historical information and the year was to be earlier than AD1000 for any entry, Domino would misinterpret the year, and if the calling URL ended in "-1" and there were no January entries, Domino will happily serve the October entries as a partial match ("-10"). Both of those are Bad Things. Okay, the year thing is being anal. Read the entry titled "Alkyds Are Bad, M'Kay"; details like this are what my life runs on.

We also need to allow for categorization at this point. If there is a Category field, it will be prepended to the year-month string, and separated from that string by a single tilde. The different separator makes it easier to extract the SelectedCategory value on the $$ViewTemplate. When we do that concatenation, it needs to be permuted so that every Category value is added to every year-month value. We also need to have the year-month values sitting there solo, since [no category value] is our "All" category. The formula, then, is:

webMonths := @Unique(@Right("000" + @Text(@Year(IncludedDates)); 4) + "-" + @Right("0" + @Text(@Month(IncludedDates));2));
withCategories := @If(Category = ""; ""; Category *+ "~" *+ webMonths);
REM "Remember: the ALL category is just webMonths";
@Trim(withCategories:webMonths)

The next column is the actual function call. In the formula below, the webCells variable is the list of cell ids to write to. Again, we'll call on IncludedDates, but this time we don't need to worry about maintaining four-digit years and two-digit months/days. JavaScript does not suffer from any ambiguities when addressing elements by id. (Besides, I didn't include the padding code in the 31 nearly-identical table cell fields you've already put on the $$ViewTemplate, and it's a hell of a lot easier to minimize the importance of padded values in this article than it would be to persuade any of you to go back to the template and edit 31 fields.) The rest of the formula is fairly straightforward; we just need to create a relative link to the document, include the event title or entry subject (as the case may be), and add whatever text we want to appear on mouseover (if any):

webCells := @Implode(@Text(@Year(IncludedDates)) + "_" + @Text(@Month(IncludedDates)) + "_" + @Text(@Day(IncludedDates));";");
linkValue := @URLEncode("Domino" @ViewTitle) + "/" + @Text(@DocumentUniqueID);
mouseOverString := (Whatever you want it to be, it's just a string);
"addEntry('" + webCells + "','" + EventTitle + "','" + linkValue + "','" + mouseOverString + "');"

Again, if you compute that value on the form, it will create less of a load on the server, but it's only practical to do so if you are using a custom database design.

That just about covers everything — in a perfect, error-free world where all months and all categories contain documents, and no entry ever starts in one month and ends in another. Let's take the second case first: an entry that starts in one month and ends in another. As you know, a document either belongs to a category or it doesn't; you can't compute column values based on he category a document is currently appearing in. If you are looking at this month (September 2004), the cells in the table will have ids like 2004_9_1, 2004_9_2, 2004_9_3, and so forth. If there is an entry that starts this month but ends in October, it will try to write to 2004_10_1, which doesn't appear anywhere on the page. Error! No probs — that's why there's a try-catch in the addEntry() function. If an error occurs (and we actually expect errors on a regular basis), it will generate an Exception, which is handled in the catch block. (The catch block is set to accumulate error messages into a variable called error, which you can use for debugging if you want.)

The other problem, though, isn't something that can be handled with try-catch. Did you know that "<h2>No Documents Found</h2>" is not valid JavaScript?. If your users ever come to a month and category combination that contains no documents, the page blows up and you get a trouble ticket. Not something any of us want to design into a database, right? The answer to that is actually fairly simple: block comments. That's why I've waited until now to have you embed the view on the $$ViewTemplate. And do embed it, will you — $$ViewBody is an R4.x thing, and I can't think of a single good reason to use it, ever, in later versions. So, go to the $$ViewTemplate, after the calendar table, and enter:

<script language="javascript" type="text/javascript">
/*
[EMBED VIEW HERE using Create->Embedded Element->View]
*/
</script>

In the Embedded View properties, select "Display using view's display property", then go to the second tab and make sure that column headers are not being displayed. You can allow quite a few lines to display — the output for each document is very small and there's no good way to do ViewNextPage or even to let the user know that there are more documents to see in a given month. I usually leave it set at zero.

Have you noticed the remaining problem? The comments prevent errors when the no documents message is returned, but now any JavaScript that is written to the document lives inside JS comments, and so cannot run. That means we need to make one minor change to that last formula to add a "close comment" at the beginning and an "open comment" at the end. It'll look weird if anyone does a View->Source of your calendar, but sometimes leaving mysteries out there is good for the geeks. You can add an explanation in the comments if you're worried about turning up on The Daily WTF.

webCells := @Implode(@Text(@Year(IncludedDates)) + "_" + @Text(@Month(IncludedDates)) + "_" + @Text(@Day(IncludedDates));";");
linkValue := @URLEncode("Domino" @ViewTitle) + "/" + @Text(@DocumentUniqueID);
mouseOverString := (Whatever you want it to be, it's just a string);
"*/ addEntry('" + webCells + "','" + EventTitle + "','" + linkValue + "','" + mouseOverString + "'); /*"

As for other errors, well, it's really up to you to handle things like missing start or end dates on the calendar entry documents. What I have provided is working code, not a heat-and-serve, boil-in-the-bag application. It's not meant to be a complete solution to any business problem; it's only a workaround for the shortcomings in Domino's built-in calendar view. You have enough, now, to apply the same principles to a one-week DayTimer/Organizer-style view

Folks, that's all there is to it. It may not be obvious in the sense that it would be the first thing that jumps into your head when you think about prettying up Domino's calendar, but you can see that it's just a matter of AHA! There's no really esoteric code or anything, you can't brute-force it. It was there all along, waiting for someone to not look at it in just the right way (I believe deeply in the reality of SEP fields, and I believe that there was one around the first version of this calendar.) All I've done is notice it and tell other people about it.

I have a working model now, and when I get it nice and pretty I'm going to take the good folks at OpenNTF.org (are you a cook, and if not, why not?) up on their offer to host the sample db for download. Once it's up, I'll add a link to it and these articles in my sidebar.