Calendar of Events Application

While the Helpdesk application showed you a little bit about the calendaring functionality in the CDO library, the Calendar of Events application demonstrates the full power of CDO calendaring. The idea behind the Calendar of Events application is to allow a corporation to publish, either internally or externally, a calendar of corporate events. Since the application is based on Exchange Server and Outlook, it is rich enough to support permissions for the creator or modifier of the calendar content and easy enough for users to add new content by employing familiar tools.

The Calendar of Events application shows how to create publicly accessible calendars, use message filters in the CDO library, and use more objects in the CDO Rendering library. You will also see some of the current limitations in the CDO library and learn about ways to work around these limitations, particularly the limitations of not supporting public folder calendars and not being able to filter by category when using appointment items.

The monthly results page for the application is shown in Figure 11-23. As you can see, the application is web-based, but users can take advantage of Outlook's calendaring features to create the contents for the web-based calendar. The application dynamically connects and retrieves the information that Outlook users create in the Exchange Server database and exposes it to web clients. Outlook users can also use their Outlook client to retrieve calendar information, as shown in Figure 11-24.

click to view at full size.

Figure 11-23 The monthly view of the Events Calendar in HTML.

click to view at full size.

Figure 11-24 A monthly view in Outlook of the source calendar for the Calendar of Events application.

Setting Up the Calendar of Events Application

Before you can install the Calendar of Events application, you must have a Windows NT 4.0 Server and a client with certain software installed. Table 11-3 outlines installation requirements.

Table 11-3 Installation Requirements for the Calendar of Events Application

Required Software Installation Notes
Exchange Server 5.5 SP1 with Outlook Web Access
IIS 3.0 or higher with Active Server Pages IIS 4.0 is recommended.
CDO library (cdo.dll)
CDO Rendering library (cdohtml.dll)
Exchange Server 5.5 SP1 installs CDO library 1.21 and CDO Rendering library 1.21. Outlook 98 installs CDO library 1.21.
For the client:
A web browser
Outlook 98
You can run the client software on the same machine or on a separate machine.

To install the application, copy the Calendar Of Events folder from the companion CD to the location on your web server where you want to run the application.

Start the IIS administration program. Create a virtual directory that points to the location where you copied the Calendar Of Events files, and name the virtual directory events. Enable Execute permissions on the virtual directory. This step allows you to use the following URL to access your Events Calendar: http://yourservername/events.

Launch the Exchange Administrator program. Select Options from the Tools menu and click the Permissions tab. Make sure that the Show Permissions Page For All Objects check box is checked and the Display Rights For Roles On Permissions Page check box is checked. Create a new mailbox that will contain the Events Calendar by selecting New Mailbox from the File menu. In the Properties dialog box, fill in the information for the mailbox, such as Events Calendar for Display and events for Alias. Set the Primary Windows NT Account to the IIS anonymous user account (IUSR_servername). Click on the Permissions tab, and add yourself as a user account with permissions to this mailbox.

NOTE
Your anonymous IIS user account should be a domain account or at least assigned as the owner of a mailbox in Exchange Server. Normally, the account IIS uses to log users on to your web pages anonymously is named IUSR_servername. The Calendar of Events application uses this account when starting an ASP session to automatically log on to the Exchange Server without prompting for credentials. You will see how this is used when we step through the application. If the IIS anonymous user account is not a domain account or cannot be assigned as an owner of a mailbox in Exchange Server, change the account so that it can be assigned owner permissions.

If you use a different alias name for the Events Calendar mailbox, you will need to modify the Global.asa file. Open the Global.asa file in the Calendar Of Events folder on your IIS server. Find the line

 Application("MailboxName") = "events" 

and change the name to the alias name of the Events Calendar mailbox you created.

The application includes a file named cats.inc. Since the application allows you to filter events based on Outlook categories, you might want to change cats.inc to reflect the categories that are important to your application. If you do change the categories, you will need to specify the total number of categories you want to filter on. The following code is the sample file from the companion CD:

 <% NumberofCats = 5 Dim strCategories(5) strCategories(1) = "Business" strCategories(2) = "Competitive" strCategories(3) = "Presentations" strCategories(4) = "Hands-On Training" strCategories(5) = "Social" %> 

To change the values for your categories, modify the NumberofCats integer to be the total number of categories. Then change the Dim strCategories(5) statement to reflect the number of categories, which enables VBScript to create an array of the category names. Now type the name of the category as a string argument in one of the cells of the array.

If you specified a name for the virtual root that is different from /events, find the file named virtroot.inc, open it, and change the virtual root you created for the application.

Create a profile for Microsoft Outlook that connects to the Events Calendar mailbox you created. You can create a new profile by opening the Mail control panel applet and clicking Show Profiles on the Services tab. On the General tab of the displayed Mail dialog box, you can add a new profile. You can also create a new profile by clicking the New button on the Choose Profile dialog box when you start Outlook. If the Choose Profile dialog box does not automatically display when you start Outlook, choose Options from the Tools menu in Outlook and click on the Mail Services tab. In the Startup Settings area, select the Prompt For A Profile To Be Used option.

With Outlook opened to the Events Calendar mailbox, right-click on the Calendar folder, select Properties, and click on the Permissions tab. Set permissions for the users in your organization who need to create and edit appointments in the calendar. You do not have to set the Default permissions on the folder, so you can restrict the access permissions for each user in the organization and enable permissions for creating and deleting items without giving permissions to everyone with access to the calendar. This step will allow Outlook users with the proper permissions to view and possibly edit the calendar for the Events Calendar mailbox. To open the Calendar folder for the Events Calendar mailbox, other users would choose Open from the File menu in Outlook and then select Other User's Folder. From the displayed Open Other User's Folder dialog box, users can select and open the Calendar folder. You're finished. You can now add events to the Outlook calendar for the Events Calendar mailbox, and then test viewing those events from the URL http://yourservername/events.

CDO Sessions

The Calendar of Events application uses the Global.asa file of the Helpdesk application with a few changes. The file is modified primarily because CDO does not support accessing calendars in public folders or delegated calendars (at the time this book went to press). You can access calendars only when you are the primary Windows NT account owner of the mailbox. With ASP, you can get around this limitation by assigning the anonymous IIS user account as the primary owner of a mailbox, thereby making all users who browse your web page automatically log on to CDO using this mailbox as their default.

Because IIS uses the security context of this anonymous user account to browse web pages anonymously, you do not have to prompt the user for security credentials to enable them to log on to the mailbox, as we had to in the Helpdesk application. Instead, all you need to do is add a CDO logon to the Session_OnStart subroutine in your Global.asa. This logon method will force every new web user to log on to the Exchange Server using the mailbox you created as well as the security credentials of the anonymous user account in IIS. The Global.asa code for the Calendar of Events application is shown here:

 <SCRIPT LANGUAGE="VBScript" RUNAT="Server"> Sub Application_OnStart     On Error Resume Next     Set objRenderApp = Server.CreateObject("AMHTML.Application")     If Err = 0 Then        Set Application("RenderApplication") = objRenderApp     Else         Application("startupFatal") = Err.Number         Application("startupFatalDescription") = _             "Failed to create application object<br>" & _             Err.Description     End If     Application("hImp") = Empty     'Load the configuration information from the Registry     objRenderApp.LoadConfiguration 1, _         "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\" & _         "MSExchangeWeb\Parameters"     Application("ServerName") = objRenderApp.ConfigParameter("Server")     Application("MailboxName") = "testacct"     Err.Clear End Sub  Sub Application_OnEnd     Set Application("RenderApplication") = Nothing End Sub Sub Session_OnStart 'On Error Resume Next     Set objRenderApp = Application("RenderApplication")     hOldImp = objRenderApp.ImpID     Set Session("AMSession") = Nothing     set objOMSession = Server.CreateObject("MAPI.Session")     bstrProfileInfo = Application("ServerName") + vbLF + _         Application("MailboxName")     objOMSession.Logon "", "", False, True, 0, True, bstrProfileInfo      set Session("AMSession") = objOMSession     'This is a handle to the security context.      'It will be set to the correct value when CDO session is created.     Session("hImp") = objRenderApp.ImpID End Sub  'While calling the Session_onEnd event, IIS doesn't call us in  'the right security context.  'Workaround: current security context is stored in Session '(look at logon.asp) and then gets restored in Session_onEnd  'event handler. ' 'All CDO and CDOHTML objects stored in the Session object 'need to be explicitly set to Nothing between the two  'objRenderApp.Impersonate calls below. Sub Session_OnEnd     On Error Resume Next     set objRenderApp = Application("RenderApplication")     hImp = Session("hImp")     If Not IsEmpty(hImp) Then         objRenderApp.Impersonate(hImp)     End If     'Do our cleanup. Set all CDO and CDOHTML objects inside      'the session to Nothing.     'The CDO session is a little special because we need to do      'the Logoff on it.     Set objOMSession = Session("AMSession")     If Not objOMSession Is Nothing Then         Set Session("AMSession") = Nothing         objOMSession.Logoff         Set objOMSession = Nothing     End If End Sub </SCRIPT> 

Since all users of the application will be accessing the same mailbox, you might be wondering why the code for logging on to the Exchange Server is in the Session_OnStart subroutine and not in the Application_OnStart subroutine. The main reason for creating a new session for each user to the same mailbox is to improve the performance of the application. If the application did not do this, all users would use the same CDO session to connect to the Exchange Server.

Prompting the User for Input

After a CDO session has been created for the user but before the user can start viewing the calendar, the application must ask which appointment types and month the user wants to view in the calendar. To do this, the application presents a search page with options to select the month, year, and event categories, as shown in Figure 11-25.

The next section of code is for the search page in Figure 11-25. Notice how the code figures out the current month on the web server machine and uses it as the default value in the Select Month drop-down list box. Using the current year as the point of reference, the code dynamically generates the previous year and next year in the Year drop-down list box. This page also uses a hidden control on the HTML form that will indicate to the next ASP page, Events.asp, that the user originated from the current page.

click to view at full size.

Figure 11-25 From the search page of the Events Calendar, the user can select the month, year, and event categories they want to search for.

The HTML page does not have to check whether a valid ASP session exists for the current user because the page does not use any CDO code. The CDO logon code is handled in the Session_OnStart procedure, so when the user's session has timed out, the user is automatically logged on again when the user refreshes the screen or moves to a different page. Here is the code for the search page:

 <!--#include file="cats.inc"--> <Title>Microsoft Exchange Events Calendar</Title> <center> <p><b><FONT FACE="Arial, Helvetica" SIZE=5>  Microsoft Exchange Events Calendar</font></b></P> <BR> <HR> <FONT FACE="Arial, Helvetica" SIZE=2>  <b>Search our events calendar to find a specific event. <BR> This application is powered by Microsoft Exchange Server/CDO and Outlook.</b> <FORM METHOD=POST ACTION="events.asp"> <TABLE BORDER=2 Width=60% Bordercolor="008000" cellpadding="2"  cellspacing="0" borderdarkercolor="008000" bgcolor="#FFCC00"  borderlightcolor="008000"> <% '*********************************** 'Figure out the month '*********************************** %>     <TR>     <TD>Select Month:</TD>     <TD>     <SELECT NAME="month" SIZE=1>     <%      Dim MonthArray(12)     MonthArray(1)="January"     MonthArray(2)="February"     MonthArray(3)="March"     MonthArray(4)="April"     MonthArray(5)="May"     MonthArray(6)="June"     MonthArray(7)="July"     MonthArray(8)="August"     MonthArray(9)="September"     MonthArray(10)="October"     MonthArray(11)="November"     MonthArray(12)="December" %>             <% for i = 1 to 12 %>         <% if month(now) = i then %>             <OPTION Selected Value = <%=i%>> <%= MonthArray(i) %>         <% Else %>             <OPTION Value = <%=i%>> <%= MonthArray(i) %>         <% End if %>     <% Next %>     </SELECT>          <% '************************************ 'Figure out the year '************************************ %>     <SELECT NAME="year" SIZE=1>          <% 'Figure out the current year and go back and ahead 1 year %> <% yearprevious = dateadd("yyyy",-1,date) %>     <OPTION> <% response.write year(yearprevious) %>     <OPTION SELECTED> <% response.write year(date) %> <% yearnext = dateadd("yyyy",1,date) %> <OPTION> <% response.write year(yearnext) %>     </SELECT>     </TD></TR>          <% '************************************** 'Figure out the categories '************************************** %>              <TR>     <TD>Category of Event:</TD>     <TD><SELECT NAME="Type" SIZE=1>         <OPTION SELECTED>All         <% for c = 1 to NumberofCats             response.write "<OPTION>" & strCategories(c)         next         %>     </SELECT>     </TD></TR> </TABLE>      <% '************************************** 'Create a hidden field so that we know the 'request came from calendar.asp '************************************** %> <INPUT TYPE=HIDDEN NAME="fromcalendar" VALUE="fromcalendar"><BR> <INPUT TYPE=SUBMIT VALUE="Submit Form"><INPUT TYPE=RESET  VALUE="Reset Form"> </FORM> <br> <HR> </center> </font> <BR> <FONT FACE="Arial, Helvetica" SIZE=2>  <P><b>Note:</b>  This calendar is powered by Microsoft's  <b>Exchange Server</b>. When you submit a search request, the web server dynamically  searches an Exchange Server Calendar and generates the result page according to your input. The appointments are actually created by using the Microsoft Outlook Client. </font> </BODY> </HTML> 

Displaying Views of the Calendar

When the user clicks the Submit Form button on the HTML form, the application passes the entered information to the next ASP page in the application, Events.asp. This page creates a monthly view of the information stored in the events calendar, as shown in Figure 11-26.

click to view at full size.

Figure 11-26 The code for this page is Events.asp. The code creates a monthly view of the appointments in the calendar by using HTML.

Since the CDO Rendering library does not natively support monthly calendar views, the page in Figure 11-26 creates a monthly view using only HTML tables and data from the events calendar. However, the CDO Rendering library does support daily and weekly views on calendars. Therefore, when the user selects to view all events in the calendar, the application renders the calendar day numbers as hyperlinks from which the user can drill into either daily views of the calendar day or weekly views of the events for the entire calendar week. Weekly views are available only when the user clicks on the hyperlink for the Sunday hyperlink. Daily views are available on all other calendar days. Both view types are generated by the CDO Rendering library.

This page also stores values for the month, day, and year in ASP-session scope variables so that the application can remember the values in other pages. Storing these values also enables the application to create filters on the appointments contained in the calendar folder so that only the appointments for the specified month appear in the calendar. Let's take a look at the application and associated CDO objects in more detail.

Filtering Events from the Calendar

So that only certain events appear in the calendar, the application uses the MessageFilter object in the CDO library. The MessageFilter object is available in any Messages collection and allows you to specify the criteria that messages must meet before they are added to the collection. When you instantiate a new Messages collection, by default a MessageFilter object is created without filters on the content.

The MessageFilter object allows you to filter on built-in and custom properties for message objects in the Messages collection. There is one caveat, however, with the MessageFilter object: if the collection contains AppointmentItem objects (and a calendar folder does), the MessageFilter object offers only a limited subset of its functionality. This subset is the ability to filter only on the start and end times for the items in the collection. For this reason, in the source code for the Events.asp file, you'll notice a MessageFilter object that uses the month selected by the user as the input for the filter's start and end times. You will also notice that custom VBScript code searches through the filtered set of appointments to figure out which appointments actually have the category the user selected. This functionality is implemented as custom VBScript because the MessageFilter object lacks this functionality for appointments. The following code creates and sets the MessageFilter object for the events calendar:

 <% '****************************************************************** 'Filter all appointments except the requested month's appointments '****************************************************************** 'Get the Calendar Folder Set Session("objFolder") = objOMSession.GetDefaultFolder(0) set objFolder = Session("objFolder") set objAppointment = objFolder.messages.getfirst() set objAppointments = objFolder.Messages set objMsgFilt = objAppointments.Filter 'Calculate the start and end dates based on the month the  'user selected StartDate = EventMonth & "/" & "1" & "/" & EventYear EndDate = EventMonth & "/" & "1" & "/" & EventYear EndDate = DateAdd("m",1,EndDate) objMsgFilt.Fields(ActMsgPR_START_DATE) = EndDate objMsgFilt.Fields(ActMsgPR_END_DATE) = StartDate set Session("objAppointments") = objAppointments Session("LastDayofMonth") = iLastDay %> 

As the preceding code illustrates, the first step in creating a filter is retrieving the Messages collection you want to apply the filter to. Since the MessageFilter object is a child of the Messages collection, you need to retrieve it by using the Filter property on the Messages collection. If the collection you were filtering did not contain appointments, you could create your filter by setting the properties on the MessageFilter object.

Because we are retrieving the calendar folder for the events calendar mailbox, we need to specify properties for the start and end times by using the Fields collection of the MessageFilter object. The specific identifiers for these two properties are &H00600040 for the start date and &H00610040 for the end date. (These identifiers are defined in the file Amprops.inc, which is included with the Calendar of Events files on the companion CD.) To create the filter, all you need to do is set these identifiers, in the Fields collection, to your values. Be careful when setting these properties—they don't work in the way you think they should.

For example, you would think that when you specified a value for the start date, you would enter in the first day for the filter, which would make CDO return every appointment starting from the day you entered and moving forward in time. However, the way the code is implemented in the library, the MessageFilter object actually returns any appointments that start on that day or occurred before that date. For the end date, the filter returns any appointments that end on the date or occur after that date. Therefore, in the Calendar of Events application, the first day of the month selected by the user is specified as the start date value for the filter, and the first day of the next month after the month selected by the user is specified for the end date value. These values return all appointments in the specified month.

Now that we have all the appointments in the month, we need to manually filter them by the category the user specified. For example, if the user specified only hands-on training events, we must provide a subroutine to filter and print only hands-on training events. The next snippet of code does this for you. It uses the For...Each statement in VBScript to scroll through the filtered Messages collection we created. While the code loops through the collection, it checks to see whether the current appointment starts on the current day. If the appointment does start on the current day, the code checks to see whether the user selected a specific category. If the user did select a category, the code loops through the categories on the AppointmentItem object, checking to see whether the object contains the specified category. If the category is found, the application prints out the appointment. If the category is not found, the code moves to the next appointment in the collection.

You might have noticed a variable in the code named AlreadyPrinted. I added this variable to help you enable the application to support users who specify multiple categories to search on. Imagine that you have an event that is marked for the Business and Hands-On Training categories. If you allow users to specify both search categories such that any event categorized as either Hands-On Training or Business is identified, you will run into problems with duplicate printing of events because the values for appointment categories added in Outlook are not guaranteed to be in a particular order. The Categories field for one appointment could have the values Business, Hands-On Training, Competitive while another could contain the same values but in a different order: Hands-On Training, Competitive, Business. When this is the case, both events print.

The code uses a For...Each loop to scroll through the categories collection. After it finds the targeted category value and prints the item on the calendar, the code changes the AlreadyPrinted variable to True. Therefore, if this item meets other subsequent categories the user selected, it won't be duplicated on the calendar. Why does the code use a variable rather than contain an Exit For statement? I used a variable because it gives you more flexibility if you want to change the code to perform other functionality. However, an Exit For statement would work in this case just as well. Here's the code that filters the appointment categories:

 <% '******************************************************* 'Check to see whether event should be written '******************************************************* %> <% AlreadyPrinted = FALSE     for each objappointment in objAppointments         StartTime = objappointment.StartTime         'Check the day of the message         oDay = DAY(StartTime)         'Figure out friendly start time         if Hour(StartTime) = 12 then  '12:00 PM             if Minute(StartTime) = 0 then '0 minutes                 dStartTime = "12:00 PM"             else                 dStartTime = "12:" & Minute(StartTime) & " PM"             end if         elseif Hour(StartTime) > 12 then             if Hour(StartTime) > 11 then  'PM                 if Minute(StartTime) = 0 then '0 minutes                     dStartTime = (Hour(StartTime)-12) & ":00 PM"                 else                     dStartTime = (Hour(StartTime)-12) & ":" & _                         Minute(StartTime) & " PM"                 end if             end if         else             if Hour(StartTime) = 0 then '12 AM                 if Minute(StartTime) = 0 then '0 minutes                     dStartTime = "12:00 AM"                 else                     dStartTime = "12:" & Minute(StartTime) & _                         " AM"                 end if             else                 if Minute(StartTime) = 0 then '0 minutes                     dStartTime = Hour(StartTime) & ":00 AM"                 else                     dStartTime = Hour(StartTime) & ":" & _                         Minute(StartTime) & " AM"                 end if             end if         end if 'friendly start time                  if oDay = (i-iDayMarker) then             'Check the categories if AllBit = 0             if AllBit = 1 then                 'Check to see if all-day event                 if objappointment.AllDayEvent = True then                     response.write "<B>All Day Event" & _                     "&nbsp&nbsp&nbsp</B><A HREF='details.asp?id=" & _                     objappointment.id & _                     "' style='color: rgb(255,0,0)'>" & _                     objappointment.Subject & "</a><BR>"                 else                     response.write "<B>" & dStartTime & _                     "</B> &nbsp<A HREF='details.asp?id=" & _                     objappointment.id & _                     "' style='color: rgb(0,0,255)'>" & _                     objappointment.Subject & "</a><BR>"                 end if             else                  'Check categories!                                     if IsEmpty(objappointment.Categories) then                 'No cats                 else                     for each category in objappointment.Categories                         if InStr(category,EventType) then                             if Not(AlreadyPrinted) then                                 if objAppointment.AllDayEvent = True then                                     response.write _                                     "<B>All Day Event" & _                                     "&nbsp&nbsp&nbsp" & _                                     dStartTime & "</B> &nbsp" & _                                     <A HREF='details.asp?id=" & _                                     objappointment.id & "' style" & _                                     "='color: rgb(255,0,0)'>" & _                                     objappointment.Subject & _                                     "</a><BR>"                                 else                                     response.write "<B>" & _                                     dStartTime & "</B> &nbsp<A " & _                                     HREF='details.asp?id=" & _                                     objappointment.id & "' style" & _                                     "='color: rgb(0,0,255)'>" & _                                     objappointment.Subject & _                                     "</a><BR>"                                 end if                                 AlreadyPrinted = TRUE                             end if 'Not Already Printed                         end if 'Categories Match                     next                 end if 'Check categories             end if 'All Bit         end if 'oDay     AlreadyPrinted = FALSE 'Reset Already Printed     Next set objappointment = nothing n=1 %> 

Displaying a Weekly View

When the user is not filtering by category and clicks on the hyperlink for any Sunday in the calendar, a weekly view appears showing events for the current week, as illustrated in Figure 11-27 .

click to view at full size.

Figure 11-27 The Weekly view in the Events Calendar. This view is reached by clicking on the hyperlink for any Sunday in the calendar.

The weekly view is implemented in the events calendar by using the CDO Rendering library. While you could create your own weekly view, it is much easier to leverage the CDO Rendering library and customize the way it renders the view using the library's objects. The CDO Rendering library offers rich object support for customizing what is rendered by the library.

As illustrated in the Helpdesk application, the way to get started with the CDO Rendering library in an application is to create either a container or an object renderer by using the CreateRenderer method on the RenderingApplication object. The Events Calendar application creates a container renderer because the items rendered by the application are contained in a calendar folder. However, unlike the Helpdesk application, which used TableView objects to render its data, the Events Calendar application uses CalendarView objects. The Events Calendar application also customizes the patterns and formats of the CalendarView object to specify the graphics to be used when rendering information. The placement of the CalendarView object in the CDO Rendering library is shown in Figure 11-28. Most of the properties of the CalendarView object are filled in by default when you instantiate a CalendarView object, so you don't have to set these properties unless you want to customize the way CDO renders the information into HTML.

click to view at full size.

Figure 11-28 The CDO CalendarView object is a child object of the Views collection in the CDO Rendering library.

As with the Helpdesk application, we need to set a data source to be rendered. In this case, the data source is the filtered set of appointments we created for the calendar. To instantiate a CalendarView object, we retrieve from our data source a view from the Views collection. Since the daily view always has an index of 1 in the Views collection, the code grabs the daily view for the calendar using this index, instead of scrolling through all the views in the collection. As you will see later, the daily view is morphed into a weekly view by using the Mode property for the CalendarView object.

Once we have a CalendarView object, we can manipulate the Format and Pattern objects of the ContainerRenderer object to add custom HTML rendering. The Format object controls how a particular property is rendered by the CDO Rendering library. For example, you can pass to the Formats collection either the ID of a built-in property or the name of a custom property to create a custom HTML format for that property when the property is rendered by the library. This code shows an example from the weeklyview.asp file:

 'sensitivity Set objFormat = oCalContRenderer.Formats.Add( _     ActMsgPR_SENSITIVITY, Null) 

After adding the format, you can retrieve the Patterns collection on the Format object to specify how a particular value for a property should be formatted. In the previous example, if the sensitivity of the appointment in the calendar is set to Private, an image of a key is placed before the text of the appointment. You can make the patterns more complex because the values for the patterns will accept any legal HTML tags.

You can specify a default pattern for the Pattern objects if a particular property does not contain one of your values. To the Patterns collection, just add a Pattern object that takes the asterisk character (*) as its value. You then specify the HTML tags CDO should use to render the unspecified value types. The code for both specified and unspecified values is shown here:

 Set objPatterns = objFormat.Patterns bstrHTML = bstrImgSrc + _     "/images/private.gif WIDTH=13 HEIGHT=13 BORDER=0>" objPatterns.Add 1, bstrHTML   ' personal objPatterns.Add 2, bstrHTML   ' private objPatterns.Add 3, bstrHTML   ' confidential objPatterns.Add "*", ""       ' normal 

The following code shows you the other Format and Pattern object settings for the weekly view in the Events Calendar application:

 'Recurring Set objFormat = oCalContRenderer.Formats.Add( _     AmPidTag_IsRecurring, Null) Set objPatterns = objFormat.Patterns objPatterns.Add 0, "" bstrHTML = bstrImgSrc + _     "/images/recur.gif WIDTH=13 HEIGHT=13 BORDER=0>" objPatterns.Add "*", bstrHTML 'Meeting status Set objFormat = oCalContRenderer.Formats.Add( _     AmPidTag_ApptStateFlags, Null) Set objPatterns = objFormat.Patterns objPatterns.Add 0, "" bstrHTML = bstrImgSrc + _     "/images/meeting.gif WIDTH=12 HEIGHT=13 BORDER=0>" objPatterns.Add "*", bstrHTML 'Location Set objFormat = oCalContRenderer.Formats.Add( )     AmPidTag_Location, Null) Set objPatterns = objFormat.Patterns objPatterns.Add "",  "" objPatterns.Add "*", "(%value%)" 

After you set your Format and Pattern objects, you can customize the way CDO renders the HTML tables it creates. The properties you need to manipulate the ContainerRenderer object are TablePrefix, TableSuffix, RowPrefix, RowSuffix, CellPattern, and LinkPattern. The following code is taken from weeklyview.asp, which sets these properties:

 oCalContRenderer.TablePrefix = _     "<table columns=%columns% border=0 cellpadding=0 cellspacing=1 " _     & "WIDTH=100% HEIGHT=10% bgcolor='#000000'>" & Chr(10) oCalContRenderer.TableSuffix = "</table>" & Chr(10) oCalContRenderer.RowPrefix = "<tr>" & Chr(10) oCalContRenderer.RowSuffix = "</tr>" & Chr(10) oCalContRenderer.CellPattern = "<font size=2>%value%</font>" oCalContRenderer.LinkPattern = "<a href='details.asp?id=%obj%' " _     & "target='_top'>%value%</a>" 

The TablePrefix property allows you to customize the HTML table that CDO renders before CDO creates a separate table for the actual item content in the data source. By customizing TablePrefix, you can add custom HTML tags before CDO renders any content to the browser. This property works in conjunction with the TableSuffix property, which specifies what to render at the end of the HTML table created by the TablePrefix property.

The RowPrefix property allows you to customize the HTML that appears at the beginning of each HTML table row. You can use this property to change the way the row is rendered—for example, modifying the height, width, or alignment of the items in the row. RowSuffix specifies the HTML that should appear after the row and is used in conjunction with RowPrefix.

The CellPattern property specifies the HTML for every cell in each table row that you render. In the code for the Events Calendar application, the CellPattern property is set to a font size of 2 and is set to display the value contained in the appointment. This property does not affect any hyperlinked values in your cell, and CDO always generates a link for exactly one cell in each row. So use the CellPattern in conjunction with the LinkPattern to create fully functional table rows, because the LinkPattern property affects only the hyperlink cell in your table. As you can see in the code, the application sets the LinkPattern for the hyperlinked cells so that the hyperlink points at the details.asp file, and it passes the entryID that corresponds to the current appointment clicked on by the user to this ASP using the %obj% token. It also dynamically prints out the subject of the appointment by using the %value% token.

The final section of the code sets some options on the ContainerRenderer object, such as the start and end times for the business day, and the time zone for the appointment dates and times. This section also morphs the daily view into a weekly view by setting the Mode property for the CalendarView object to be CdoModeCalendarWeekly(1) rather than CdoModeCalendarDaily(0). The code then calls the RenderAppointments method on the CalendarView object, which takes as its arguments the starting date for rendering information and the output stream used to send the generated HTML. Normally for the output stream parameter, you would type Response, which tells CDO to render the HTML to the Response object of the ASP object library. The following code implements this functionality for the Events Calendar application:

 oCalContRenderer.TimeZone = objOMSession.GetOption("TimeZone") 'Set Sunday as first day of week oCalContRenderer.FirstDayOfWeek = 7 oCalContRenderer.Is24HourClock = _     objOMSession.GetOption("Is24HourClock") oCalContRenderer.BusinessDayStartTime = _     objOMSession.GetOption("BusinessDayStartTime") oCalContRenderer.BusinessDayEndTime = _     objOMSession.GetOption("BusinessDayEndTime") oCalContRenderer.BusinessDays = _     objOMSession.GetOption("WorkingDays") oCalView.NumberOfUnits = 1 curDay = CDate(curDay) oCalView.Mode = 1 oCalView.RenderAppointments curDate,Response 

Displaying a Daily View

When the user is not filtering by category and clicks on the hyperlink for any day in the calendar week except Sunday, a daily view appears, as shown in Figure 11-29.

The code for rendering the daily view in the file dailyview.asp is similar to the code for rendering a weekly view except for two differences. First, we keep the daily view as the view in the Mode property for the CalendarView object rather than change it, as we did in the weekly view rendering code. Second, we must explicitly render all-day events separately from appointments in the view. In the weekly view mode, CDO automatically renders all-day events.

click to view at full size.

Figure 11-29 The Daily view for the Events Calendar application allows users to see more details about the events on a specific day.

The way to render events separately from appointments in a daily view is to explicitly call the RenderEvents method on the CalendarView object before calling the RenderAppointments method. The RenderEvents method takes the same parameters as the RenderAppointments method, which includes the date for which you want to render the events and the output stream that will place the HTML code created by the method. The following code shows you how to render both events and appointments using RenderEvents and RenderAppointments:

 oCalView.RenderEvents curDate,Response oCalView.RenderAppointments curDate,Response 

Displaying the Details of an Event

When a user clicks on any hyperlink (from monthly, weekly, or daily view) to get the details for an event, details.asp is called. This ASP page displays details about the event so that a user can find the event location and obtain any supporting materials. The ASP page also automatically supports rendering and viewing attachments, because it uses the CDO Rendering library to display the text describing the event. The user interface for this page is shown in Figure 11-30.

click to view at full size.

Figure 11-30 The Details page for an event in the Events Calendar application can render rich text as well as hyperlinks because it uses the CDO Rendering library.

The code in the details.asp page is pretty straightforward, but it shows you how to use other objects in the CDO Rendering library, such as the ObjectRenderer object. The ObjectRenderer object (as opposed to the ContainerRenderer object) is used because you are displaying properties from an individual CDO object, such as an appointment. You should use the ContainerRenderer object only if you are rendering a collection of items, such as all the messages in your Inbox. Figure 11-31 shows the ObjectRenderer object hierarchy.

To create an ObjectRenderer object, all we need to do is pass the constant CdoClassObjectRenderer(2) to the CreaterRenderer method of the RenderingApplication object. (There are a lot of "renders" in that last sentence!) Here is the code from details.asp:

 'Create an ObjectRenderer set objObjRenderer = objRenderApp.CreateRenderer(2) 

After creating the ObjectRenderer, we need to set the DataSource property for it. This property is the same as the DataSource property for a ContainerRenderer object except for one fundamental difference: the ObjectRenderer object can take only individual items as its data source, such as an AddressEntry, an AppointmentItem, or a Message object.

click to view at full size.

Figure 11-31 The ObjectRenderer object in the CDO Rendering library is used to display properties of individual items rather than of collections.

Now that the data source is set, we can start using some of the methods on the ObjectRenderer object. To render the details of the event, we need to render individual properties off the AppointmentItem object, such as name, location, and details. The ObjectRenderer object gives us a method, RenderProperty, that allows us to render individual properties off the object. The RenderProperty method takes three arguments:

  • The property ID for built-in properties, or the name of the property if it is a custom property that we want to render
  • A reserved argument for which you should always pass 0 as the value
  • The output stream, to pass the HTML that the CDO Rendering library generates

Normally, you would type Response for this argument to return the HTML to the browser. The following code is taken from details.asp. It shows how RenderProperty renders the different properties on an AppointmentItem object.

 <table border="1" width="100%"> <tr>     <td width="24%" bgcolor="#FFFF80"><big><em><strong>     Event Name:</strong></em></big></td>     <td width="76%" bgcolor="#8000FF"><strong><font face="Tahoma">     <% objObjRenderer.RenderProperty ActMsgPR_SUBJECT, 0, Response %>     </font></strong> </td> </tr> <tr>     <td width="24%" bgcolor="#FFFF80"><big><em><strong>     Event Location:</strong></em></big></td>     <td width="76%" bgcolor="#8000FF"><strong><font face="Tahoma">     <%      if objEvent.Location = "" then         response.write "None specified"     else         objObjRenderer.RenderProperty AmPidTag_Location, 0, _         Response      end if     %>     </font></strong></td> </tr> <tr>     <td width="24%" bgcolor="#FFFF80"><big><em><strong>     Start Date:</strong></em></big></td>     <td width="76%" bgcolor="#8000FF"><strong><font face="Tahoma">     <% objObjRenderer.RenderProperty AmPidTag_ApptStartWhole, 0, _     Response %>     </font></strong></td> </tr> <tr>     <td width="24%" bgcolor="#FFFF80"><big><em><strong>     End Date:</strong></em></big></td>     <td width="76%" bgcolor="#8000FF"><strong><font face="Tahoma">     <% objObjRenderer.RenderProperty AmPidTag_ApptEndWhole, 0, _     Response %>     </font></strong></td> </tr> <tr>     <td width="24%" bgcolor="#FFFF80"><big><em>     <strong>Duration:</strong></em></big></td>     <td width="76%" bgcolor="#8000FF"><strong><font face="Tahoma">     <% objObjRenderer.RenderProperty AmPidTag_ApptDuration, 0, _     Response %>     minutes</font></strong></td> </tr> <tr>     <td width="24%" bgcolor="#FFFF80"><big><em><strong>     Event Details:</strong></em></big></td>     <td width="76%" bgcolor="#8000FF"><strong><font face="Tahoma">     <%      if objEvent.Text = "" then         response.write "None specified"     else         objObjRenderer.RenderProperty ActMsgPR_RTF_COMPRESSED, _         0, Response     end if     %>     </font></strong></td> </tr> </table> 

Most of the properties are pretty straightforward, but one of them requires careful handling when rendering because it is quite powerful and can easily cause problems if you do not handle the output correctly. This property is the last one rendered by the application, ActMsgPR_RTF_COMPRESSED. It is the message body for the item. When you use the RenderProperty method with this property, the Rendering library will automatically convert the rich-text formatting in the message to HTML. This is a powerful feature and one of the primary reasons you should use the Rendering library to display the body of an item.

However, one aspect of this method to watch out for is that in addition to converting the body of an item, the method also converts the attachments in the item to hyperlinks. While this is useful and makes your application very powerful, CDO defaults the hyperlinks it creates to retrieve a file named read.asp in the Exchange virtual root on the IIS server. Remember how IIS defines ASP applications—by virtual root. Now can you see the inherent problem in this? When the user clicks on this default hyperlink, IIS starts a new ASP application under the Exchange virtual root. This causes the Outlook Web Access logon screen to appear, since ASP applications cannot share session and application states, and OWA has no idea that the user has already authenticated with the Calendar of Events application. Further, if the user enters her security credentials, the attachment will not appear in the browser; instead, OWA will open the Inbox of the user—not the desired functionality.

To fix this problem, we must first change the default virtual root of RenderingApplication, which is the Exchange virtual root. To do this, we use the VirtualRoot property on the RenderingApplication. The VirtualRoot property takes a string argument that sets the beginning of the URL when you render items. In this case, we need to change the VirtualRoot property to point to the virtual root we set in our virtroot.inc file. The following code does this:

 'Change the virtual root for the rendering application Set objRenderApp = Application("RenderApplication") objRenderApp.VirtualRoot = virtroot 

The next problem we have to resolve is what file to use for the Read.asp file that RenderingApplication is creating a hyperlink to. Well, Outlook Web Access (OWA) happens to provide a read.asp that renders attachments you click on in its browser window. The OWA read.asp will automatically download and open the attachment on the machine of the user. In addition, the read.asp file will launch the application in place in the browser if the user's browser supports this option. Otherwise, the read.asp file will prompt the user to download and view the file.

To add the OWA read.asp to the Calendar of Events application, some code has to be modified. The main code modification has to be done to session and local variable names, because the default read.asp for OWA uses variable names different from those in the Events Calendar application. The code that checks for a valid session also has to be modified since the two applications use different types of session-checking code.

The way attachments are rendered to the browser is the same for both applications. First, the application parses the query string, which contains an att variable. This variable contains the attachment record key, which is a unique identifier used to retrieve, from the Attachments collection of the Message object, the particular attachment the user clicked on. Once this attachment is retrieved, the application figures out the attachment's filename so that when the user chooses to save the file after bringing it up, the browser uses the same filename as the filename of the original item. Finally, the application adds a header to the Response object, which tells the browser that it is going to send down data to the browser. Then the application uses the RenderProperty method of ObjectRenderer to stream the binary data of the attachment to the browser. Once the data is streamed down, the attachment opens. The following code implements this functionality:

 szAttach = Request.QueryString("att") nPos = InStr(1, szAttach, "-", 0) nPos = InStr(nPos+1, szAttach, "-", 0) nPos2 = InStr(nPos+1, szAttach, "-", 0) If nPos2 = 0 Then     nPos2 = Len(szAttach)+1 End If szRecordKey = Mid(szAttach, nPos+1, nPos2-(nPos+1)) szAttachName = Mid(szAttach, nPos2+1) szObj = Request.QueryString("obj") Set objOneMsg = Session("szObj") If objOneMsg Is Nothing then     Set objOneMsg = OpenMessage(szObj)     If objOneMsg is Nothing then         ReportError1 L_errCannotGetMessageObj_ErrorMessage      ElseIf objOneMsg.ID = "" then         ReportError1 L_errMessageDeleted_ErrorMessage     End If End If Set objAttach = objOneMsg.Attachments.Item(szRecordKey) if objAttach is Nothing then     ReportError1 L_errFailGettingAttachment_ErrorMessage End If bstrFileName = "" bstrFileName=  objAttach.Fields(ActMsgPR_ATTACH_LONG_FILENAME) If bstrFileName = "" then     bstrFileName =  objAttach.Fields(ActMsgPR_ATTACH_FILENAME) End If  'For short file name compatibility, add these lines '    If bstrFileName = "" then '        bstrFileName = objAttach.Name '    End If Response.Addheader "Content-Disposition", "attachment;filename=" + _     bstrFileName Set objRenderAtt = GetMessageRenderer objRenderAtt.DataSource = objAttach objRenderAtt.RenderProperty ActMsgPR_ATTACH_DATA_BIN, 0,Response 



Programming Microsoft Outlook and Microsoft Exchange
Programming Microsoft Outlook and Microsoft Exchange, Second Edition (DV-MPS Programming)
ISBN: 0735610193
EAN: 2147483647
Year: 1999
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net