Tuesday, September 14, 2004

That Damned Calendar: The Table (Part II)

I've already described the table you'll need, and seeing as we're trying to display a view, why not let's make the form that table appears on a $$ViewTemplate for something-or-other. (While you're at it, create and save a something-or-other view to go with the $$ViewTemplate. Don't worry about the view design yet, you'll just need a view with the right name saved so you can look at the $$ViewTemplate.)

The question is, how do you make it? Well you're going to need three hidden fields at the top before you go any further. The first is StartDate, which represents the date of the first day of the month you want to display. (It's also the likely name of the StartDate field in your calendar entry forms/documents, so it won't add another field name to the database list. I like keeping things tidy when I can.) Like all of the fields on the $$ViewTemplate form, it is computed-for-display, and for the moment you can make it the first of this month:

@Adjust(@Today;0;0;1-@Day(@Today);0;0;0)

That formula will get a bit bigger when I discuss safe methods for switching dates and categories. It will be enough for you to see what the table's a-gonna look like, though, and you shall see that it is indeed pretty.

The second field is NumberOfDays, which (oddly enough) is a number representing the number of days in the month you're looking at. It is a computed-for-display number field, and its formula is:

@Day(@Adjust(@Adjust(StartDate;0;1;0;0;0;0);0;0;-1;0;0;0))

For those of you who don't follow, start with the inner @Adjust. We adjust the "first-of-the-month" date forward one month, which gives us the first of next month. We then @Adjust that date back one day, which gives us the last day of this month. @Day gives us the number of that day which, by sheer coincidence, is also the number of days in the month.

The third is a little number I like to call VarName. It's just:

@Text(@Year(StartDate)) + "_" + @Text(@Month(StartDate)) + "-"

That little ditty will be used in the table cells below to create the id values.

Personally, I like to set this form to "Treat contents as HTML". You can set it to "Treat as Notes" and mark everything as passthru HTML, but I'm going to tell you here and now that there won't be much Notes-native stuff that you can leave unmarked. The table we're creating absolutely has to be in HTML, and the view itself is going to be marked "Treat as HTML", but its content is actually going to be JavaScript. Save yourself some torment, and mark the form as "Treat content as HTML". Just do it. Stop whining! I don't care how you usually work, just do as I say! There'll be no dessert if you don't.

Make a new Designer paragraph, and make sure it's not hidden. After all, you do want something to be sent to the browser. Just to take a peek at the table, then you'll need to do some work. Start with a standard HTML heading (NOTE: the addEntry function has been edited since the original posting):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Final//EN">
<html>
<head>
<style type="text/css">
BODY {font-family: verdana; font-size: 12px; color: black}
H1 {font-size: 22px; font-style: normal; font-weight: bold; line-height: 22px; color: #ff0000}
TH {font-family: verdana; font-size: 12px; font-weight: bold; text-align: center; background-color: #ccccff; border-bottom: solid black 1px}
TD {font-family: verdana}
TABLE.outer {border: solid black 1px}
TD.spacetop {font-size: 10px; font-weight: bold; height: 15px; text-align: center; background-color: #999999; border: solid black 1px}
TD.spacebottom {font-size: 10px; font-weight: bold; height: 60px; text-align: center; background-color: #cccccc; border: solid black 1px}
TD.weekendtop {font-size: 10px; font-weight: bold; height: 15px; text-align: center; background-color: #ffcccc; border: solid black 1px}
TD.weekendbottom {font-size: 10px; font-weight: normal; height: 60px; text-align: center; background-color: #fff0f0; border: solid black 1px}
TD.weekdaytop {font-size: 10px; font-weight: bold; height: 15px; text-align: center; background-color: #ccccff; border: solid black 1px}
TD.weekdaybottom {font-size: 10px; font-weight: normal; height: 60px; text-align: center; background-color: #ffffff; border: solid black 1px}
DIV.cellcontent {height: 58px; overflow: auto;}
DIV.cellcontent A {display: block;}
</style>
<script language="javascript" type="text/javascript">
var error;
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;
}
}
}
</script>
</head>
<body>

Didja catch the try-catch? That'll be right some frikkin' important later, my son. (That phrase carries a lot more import in Newfoundland than, say, nota bene, so if you can say it to yourself in Newfanese you'll really get the gist of what I'm trying to get across here. By the way, for those of you who are from away and have never been screeched in, the name of the island is pronounced noof'n'LAND. Da little accent's on da "noof" and da big accent's on da "LAND". The "ound" is properly reduced to a barely-audible schwa-en combination. The typical foreign pronunciation, in which the word "found" can clearly be heard, is just plain wrong, and something of an insult. You may be invited in for dinner, but you'll not get more than two servings — three at the very most.) I'm not introducing the view at this point, but all it's going to do is call the addEntry function a few times.

You'll probably want to do something about the stylesheet later, particularly the colours. That I'll leave in your capable hands. In case you're wondering, setting the links inside the cellcontent divisions to display: block means that you never have to worry about creating line breaks between entries, and putting the div inside the lower part of the table cells means that the days become individually scrollable while the overall look of the calendar remains, well, calendar-looking. The multitude of sizes and shapes displayed in the native Domino calendar is a real turn-off. The calendar may be functional, but let's face it, it looks like hell.

Now, throw in the title block for your calendar. Any old HTML stuff you want to display is fine by me. After that comes our calendar table:

<table class="outer" width="735">
<thead>
<tr height="20">
<th width="105">Sunday</th>
<th width="105">Monday</th>
<th width="105">Tuesday</th>
<th width="105">Wednesday</th>
<th width="105">Thursday</th>
<th width="105">Friday</th>
<th width="105">Saturday</th>
</tr>
</thead>

That gets us all the way to the first row of the meat of the calendar. Now we need to figure out how many spacers we need before the first day of the month. Add a computed-for-display text field with the following formula:

spacer:="<td valign=\"top\"><table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\"><tr><td class=\"spacetop\">&nbsp;</td></tr><tr><td class=\"spacebottom\">&nbsp;</td></tr></table></td>";
@If(@Weekday(StartDate)=1;"";"<tr>" + @Repeat(spacer;@Weekday(StartDate)-1))

(The downloadable database NS5 version contains a fix for the @Repeat overflow error in R5.) Now for the "meat" of the table. You'll need to create 31 nearly-identical fields, all computed-for-display text fields. The first twenty-eight look like this:

dayNumber := 1;
thisdate:=@Adjust(StartDate;0;0;dayNumber-1;0;0;0);
thisday:=@Weekday(thisdate);
isWeekend := @If(thisday = 1:7;@True;@False);
@If(thisday=1;"<tr>";"") + "<td><table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\"><tr><td class=\"" + @If(isWeekend;"weekendtop";"weekdaytop") + "\">"+@Text(dayNumber)+"</td></tr><tr><td class=\"" + @If(isWeekend;"weekendbottom";"weekdaybottom") + "\"><div id=\"" + VarName + @Text(dayNumber) + " class=\"cellcontent\"></div></td></tr></table></td>"+@If(thisday=7;"</tr>";"")

The only thing that changes is the first line. In the first field, daynumber is 1, in the second it's 2, and so on. I did that on purpose. Copy-and-paste beats the crap out of type-and-type-and-type-and-type-and-type. The twenty-ninth through thirty-first are only slightly different:

dayNumber := 29;
thisdate:=@Adjust(StartDate;0;0;dayNumber-1;0;0;0);
@If(!(@Month(thisdate) = @Month(StartDate));@Return("");"");
thisday:=@Weekday(thisdate);
isWeekend := @If(thisday = 1:7;@True;@False);
@If(thisday=1;"<tr>";"") + "<td><table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\"><tr><td class=\"" + @If(isWeekend;"weekendtop";"weekdaytop") + "\">"+@Text(dayNumber)+"</td></tr><tr><td class=\"" + @If(isWeekend;"weekendbottom";"weekdaybottom") + "\"><div id=\"" + VarName + @Text(dayNumber) + " class=\"cellcontent\"></div></td></tr></table></td>"+@If(thisday=7;"</tr>";"")

Again, remember to change the daynumber value in the first line of each field. The added @Return line stops the output when needed in short months. All that remains now is the closing spacer computed-for-display text field:

spacer:="<td valign=\"top\"><table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\"><tr><td class=\"spacetop\"> </td></tr><tr><td class=\"spacebottom\"> </td></tr></table></td>";
enddate := @Adjust(StartDate;0;0;NumberOfDays-1;0;0;0);
allSpaces := @Repeat(spacer;7-@Weekday(enddate));
@If(allSpaces="";"";allSpaces + "</tr>")

and closing off the HTML document:

</table>
</body>
</html>

Now all that's left to do is save the $$ViewTemplate form and preview your view in the browser to check your work. The view isn't contributing anything but its name at this point, but if you don't have a pretty calendar table, the rest is meaningless.

Tune in tomorrow, same Bat-time, same Bat-channel, for the next installation.

12 comments:

Anonymous said...

Would you please attach a sample .nsf database to your article?

Thanks,
Brandon

Anonymous said...

Stan, thanks for doing this! Forgive me for asking, but what is BaseDate? I must've missed something somewhere.

Stan Rogers said...

BaseDate is a left-over from a previous version, I'm afraid. Read it as "StartDate". In this newer version (which is a work in progress being polished as this is published) BaseDate will be added later -- it will be a hard-coded date/time value equal to January 1, AD1, and its purpose is to make the one template work for all dates between January 1, AD1 and December 31, AD9999, independent of system date/time settings. Sorry for the mixup -- I'm editing as we speak.

Stan Rogers said...

@Brandon: I can't attach anything here. You'll need to learn to type.

Anonymous said...

Let me scrounge up the typing tutorial. Thanks for the article.

-Brandon

Anonymous said...

Thanks for the explaining the BaseDate/StartDate, Stan. I knew there had to be something with it which is why I asked. :)

I believe you're missing a + in the field formulas before the @If(isWeekend;"weekendbottom";"weekdaybottom") too. (At least I hope that's what was missing. It worked! :) )
Deb

Stan Rogers said...

Exactly spot-on. Some damage was done, it would seem, when I translated the formulas into web entities. Thanks for the extra-sharp eyes, Deb. See, I knew that what I was doin' wasn't nuttin' special. If I can figger this stuff out, so can anyone else :o)

Anonymous said...

Just a quick idea I used recently for your 31 nearly identical fields.

Create the field how you want it, call it "Somefield" and set the formula for daynumber to:

daynumber := @Right(@ThisName,"_");

Then you just copy and paste your field into the positions you need, giving you Somefield_1, Somefield_2, etc. Remove (or hide) the first field and your set. If you ever need to change the formula you just wipe the copies, modify the original and paste it all again.

(Notes 6 only of course)

Nick Partridge

Anonymous said...

Oops, and you might need to put an @TextToNumber around the daynumber formula.

Anonymous said...

1) Congrats, Stan, on finally getting a blog. You were the penultimate holdout. Now that you've done it, I guess I have to do it, too.

2) What are you doing putting this on blogger? Hello!? Blogsphere!? Sheesh!

3) If you have a sample NSF for this calendar effort, would you like to host it on OpenNTF? Then you can just link to it from here.

Anonymous said...

So what do I do to my view so documents will appear in the calendar?

Stan Rogers said...

Patience, grasshopper. You're getting a solution for free, and you'll get it in the chunks I provide. You might want to read the rant before asking again.

As for why here and not on Domino (using my own proprietary template -- I've created several for the business and have an even prettier one for myself), I've explained that already. There is no truly free Domino hosting solution available to anyone who hasn't the wherewithal to access credit. Why I can't do that will be covered in later, non-technical entries; for now I'll just tell you that the folks who say that most people are only two months away from homelessness are not exaggerating in the least. The database will go to OpenNTF.org if they'll have it. I'm sorta killing two birds with one stone here -- the old design needed an update, not just for ND6 but because I have found neater ways of doing what I did in the first place; and I wanted to get an explanation of the goings-on in the database out there and available. I really can't handle another thousand hand-holding sessions, and that's what I'll get if I just release the code to the world. I thought I'd get my thoughts out here while the redesign is going on -- I'm less likely to miss anything that way.

I like the @ThisName idea; this was adapted from an R5 design, and (while I've improved some bits) I haven't completely rethought everything, obviously. Then again, these entries are long enough without having to create version-specific formulas everywhere. If you had my forty words-per-hour typing speed, you'd be lazy too ;o)