The Calendar of Events Application


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. The application is based on Exchange Server and Outlook, so 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 you use 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 expand
Figure 11-23: The monthly view of the Calendar of Events in HTML
click to expand
Figure 11-24: A monthly view in Outlook of the source calendar for the Calendar of Events application

Setting Up the Application

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

Table 11-4: Installation Requirements for the Calendar of Events Application

Required Software

Installation Notes

Exchange Server 5.5 with the latest service pack or Exchange 2000/2003 with the latest service pack

OWA must be installed for Exchange 5.5. Exchange 2003 installs OWA by default.

IIS 3.0 or later with ASP

IIS 5.0 is recommended.

CDO library (cdo.dll) and CDO Rendering library

Exchange Server installs CDO 1.21. For CDO Rendering on a clean Exchange 2003 install, you must install OWA from Exchange 5.5 on the machine to get CDOHTML.

Client Requirements

 

A Web browser or Outlook

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 sample files to the location on your Web server from which 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 URL http://<yourservername>/events to access your Events Calendar.

Create a new user in the Active Directory with a display name of Events Calendar and an alias of events . Make sure to create a mailbox for this user. Set the mailbox rights for the user under the Exchange Advanced tab for the user in Active Directory so that the IIS anonymous user account (IUSR_ servername ) has Full mailbox access rights. Also, add your own account to have Full mailbox access rights as well.

Note  

Your anonymous IIS user account should be a domain account or should at least be assigned as the owner of a mailbox in Exchange Server. Normally, the account that 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 works 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 must 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. Because 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 must specify the total number of categories you want to filter on.

The following code is from the sample application:

 <% 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, thereby enabling 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 to match the one you created for the application.

Create a profile for Outlook that connects to the Events Calendar mailbox you created. You can create a new profile by opening the Mail applet in Control Panel and clicking Show Profiles. On the General tab, you can add a new profile. You can also create a new profile by clicking the New button in the Choose Profile dialog box when you start Outlook. If this dialog box does not automatically appear when you start Outlook, choose the Mail applet again and under Show Profiles, select the Prompt For A Profile To Be Used option.

With Outlook opened to the Events Calendar mailbox, right-click on the Calendar folder, choose 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 can choose Open from the File menu in Outlook and then select Other User's Folder. In the Open Other User's Folder dialog box, they 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 the viewing of 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 1.21 does not support accessing calendars in public folders or delegated calendars. Just a quick note, though: CDO for Exchange 2000/2003 does support this capability, but not with an Exchange 5.5 Server. It supports it only on an Exchange 2000 Server.

With CDO 1.21, you can access calendars only when you are the primary Windows 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 users for security credentials to enable them to log on to the mailbox, as we had to in the Helpdesk application. Instead, all you do is add a CDO logon to the Session_OnStart subroutine in your Global.asa. This logon method forces 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"       'Uncomment the following line and  'Please enter your Exchange servername here  'Application("ServerName") = "myserver"       Application("ServerName") = objRenderApp.ConfigParameter("Server")     Application("MailboxName") = "events"     Err.Clear End Sub      Sub Application_OnEnd     Set Application("RenderApplication") = Nothing End Sub      Sub Session_OnStart          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 the 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> 

Because all users of the application will access the same mailbox, you might wonder 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.

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

The next section of code is for the search page shown 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. 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. 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.

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, he is automatically logged on again when he 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 Ro 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 <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 Calendar Views

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 expand
Figure 11-26: The code for the Events.asp page creates a monthly view of the appointments in the calendar by using HTML.

Because 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 of 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 down 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 Sunday. 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 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. This 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. However, you should note one caveat with the MessageFilter object: if the collection contains AppointmentItem objects (as a calendar folder does), the MessageFilter object offers only a limited subset of its functionality. This subset is the ability to filter 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. Because the MessageFilter object is a child of the Messages collection, you must retrieve it by using the Filter property on the Messages collection. If the collection you are filtering does not contain appointments, you can create your filter by setting the properties on the MessageFilter object.

Because we are retrieving the calendar folder for the events calendar mailbox, we must 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 in the companion content.) To create the filter, all you do is set these identifiers in the Fields collection to your values.

Be careful when you set these properties ”they don't work the way you might expect. For example, you might think that you specify a value for the start date by entering the first day for the filter, which would make CDO return every appointment starting from the day you specified 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 must manually filter them by the category the user specified. For example, if the user specifies 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. It uses the For Each... Next 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 might have the values Business , Hands-On Training , Competitive , while another might contain the same values but in a different order: Hands-On Training , Competitive , Business . When this is the case, both events will print.

The code uses a For Each...Next 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 offers more flexibility if you want to change the code to perform other functionality. However, an Exit For statement would work just as well in this case. 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 categories             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 expand
Figure 11-27: The weekly view in the Events Calendar

The weekly view is implemented in the Events Calendar by using the CDO Rendering library. You could create your own weekly view, but 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 it renders.

As illustrated in the Helpdesk application, the way to get started with the CDO Rendering library in an application is to create a container or an object renderer by using the CreateRenderer method on the RenderingApplication object. The Calendar of Events application creates a container renderer because the items rendered by the application are contained in a calendar folder. However, unlike the Helpdesk application, which uses TableView objects to render its data, the Calendar of Events application uses CalendarView objects. The Calendar of Events 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 the 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 expand
Figure 11-28: The CDO CalendarView object is a child object of the Views collection in the CDO Rendering library.

As in 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. The daily view always has an index of 1 in the Views collection, so 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 Calendar of Events 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 Calendar of Events 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 you use CellPattern in conjunction with 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 property for the hyperlinked cells so 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 by 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 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 Calendar of Events 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("BusinessDays") 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.

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

The code for rendering the daily view in the file dailyview.asp is the same as the code for rendering a weekly view except for two main 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.

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 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 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 expand
Figure 11-30: The Details page for an event in the Calendar of Events 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.

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

To create an ObjectRenderer object, all you 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 must 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.

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 has a method, RenderProperty , that allows you 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 you want to render

  • A reserved argument, for which you should always pass 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 CDO Rendering library automatically converts the rich-text formatting in the message to HTML. This is a powerful feature and one of the primary reasons you should use the CDO 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. This is useful and makes your application very powerful, but 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? 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 because ASP applications cannot share session and application states, and OWA has no idea that the user has already been authenticated with the Calendar of Events application. Furthermore, if the user enters her security credentials, the attachment will not appear in the browser; instead, OWA will open the user's Inbox ”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, OWA happens to provide a read.asp file that renders attachments you click on in its browser window. The OWA read.asp automatically downloads and opens the attachment on the user's machine. In addition, the read.asp file launches the application in place in the browser if the user's browser supports this option. Otherwise, the read.asp file prompts the user to download and view the file.

To add the OWA read.asp to the Calendar of Events application, you must modify some code. The main modification has to be done to session and local variable names because the default read.asp for OWA uses different variable names from those in the Calendar of Events application. The code that checks for a valid session must also be modified because 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 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 filename 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 2003
Programming MicrosoftВ® OutlookВ® and Microsoft Exchange 2003, Third Edition (Pro-Developer)
ISBN: 0735614644
EAN: 2147483647
Year: 2003
Pages: 227
Authors: Thomas Rizzo

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