|
|
|
Although I'm glad for a Calendar control that is included with ASP.NET, it is
certainly barebones in some ways. I have no doubt that for some
applications, you will want to extend this control or maybe buy a more robust
one from a control developer.
First of all, there is no databinding with this control. You have to do
everything on the DayRender event which fires for each day as
the calendar HTML is built. While this isn't hard to do, you do need to
worry about database hits, especially in any kind of high traffic
scenerio. You don't want to be hitting the database every time an
individual day is rendered. There are various ways to manage
this. I decided to use a datatable and some caching to minimize database
roundtrips. You may want to use something else, like an array or some
other collection. You may want to build a "Show" class and build a
collection of "Show" objects. Or you might want to throw the data out to
XML once a day and read that into a datatable. Lots of options to
experiment with.
Once I have my datatable of show dates for the current month, I then do a select
on my datatable for the current day that is rendering and see if I have any
matches. If so, I write them out to a literal control for that day.
I'm not completely happy with this method, as it seems rather inefficient, but
it will have to do for now.
Originally I was going to have a "Detail" link for each show and render the show
details to a label or repeater on my calendar page. The way I planned to
do this was to create an href back to the page with the ItinID on the
querystring. Keep in mind you are limited in what controls you can add to
a particular day as it renders. You can't use controls that fire events,
which severely limits your options.
From the help:
Note Since the DayRender event is
raised while the Calendar control is being rendered, you cannot add a
control that can also raise an event, such as LinkButton. You can only add
static controls, such as System.Web.UI.LiteralControl, Label, Image, and
HyperLink.
Okay, that sucks. Unfortunately, when you pass a querystring back to the
page, certain events don't fire. Plus you have to keep track of the month
you want to display yourself and reinitialize your data. It gets to be
quite the hassle. So I took the easy (or easier) way out and decided to
render the details in an IFRAME. I may go back to my original idea in a
followup article, since I understand stuff a bit better now and maybe I can get
everything to cooperate.
So let's get the data loaded up first. In part 3, I'll explain getting the
show details to render.
|
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="ItinSample.aspx.vb" Inherits="swartzentrubernet.ItinSample" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
</HEAD>
<body MARGINWIDTH="0" MARGINHEIGHT="0">
<form id="Form1" method="post" runat="server">
<center>
<table BORDER="1" CELLPADDING="10">
<tr bgcolor="#99cc99" bordercolor="#cc6600">
<td>
<asp:Label ID="lblMsg" Runat="server" CssClass="Alert"></asp:Label>
<table border="0" cellpadding="4">
<tr valign="top">
<td><asp:Calendar OnVisibleMonthChanged="ChangeMonth"
OnDayRender="Calendar1_DayRender" DayStyle-Height="75" DayStyle-Width="75"
DayStyle-verticalalign="Top" DayStyle-Font-Size="8" NextPrevFormat="FullMonth"
SelectionMode="Day" TitleStyle-Font-Bold="True" TitleStyle-Font-Name="Verdana"
TitleStyle-Font-Size="14" BackColor="white" BorderColor="#FF6633"
BorderStyle="Dashed" CellPadding="2" CellSpacing="2" Runat="server"
id="Calendar1" OtherMonthDayStyle-ForeColor="#C0C0C0"
DayStyle-BorderStyle="Solid" DayStyle-BorderWidth="1"
TodayDayStyle-ForeColor="Red" Height="600"
Width="750"></asp:Calendar></td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</form>
</body>
</HTML>
|
|
You can see this is a simple page, mainly just setting the calendar
properties. There are 2 events we need to handle for the basic calendar
page.
-
OnVisibleMonthChanged
- This event is triggered when the user clicks the previous or next month on
your calendar.
-
OnDayRender - This is the event that fires as each calendar
day is rendered to the browser. It will fire once for each day
displayed. Keep in mind it may display a few days from the month prior
and the month after your current month.
The other thing I did was set the SelectionMode property to
Day, which means you can only select a day at a time rather than a
range. One enhancement would be to let the user select a series of days
and render all the shows during that time frame.
|
Option Strict On
Option Explicit On
Imports System
Imports System.Text
Imports System.Drawing
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.UI.WebControls
Imports System.Web.UI
Imports System.Web.Caching
Public Class ItinSample
Inherits System.Web.UI.Page
Protected WithEvents Calendar1 As
System.Web.UI.WebControls.Calendar
Private cnString As String =
ConfigurationSettings.AppSettings("test")
Private dtDates As DataTable
Protected WithEvents lblMsg As
System.Web.UI.WebControls.Label
Private curMonth As DateTime
Private noDatesForMonth As Boolean = False
Private Sub Page_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
'Put user code to initialize
the page here
If Not Page.IsPostBack Then
curMonth
= Convert.ToDateTime(DateTime.Today.Month.ToString & "/1/" &
DateTime.Today.Year.ToString)
Calendar1.VisibleDate
= curMonth
LoadCacheForMonth(curMonth)
End If
End Sub
Protected Sub ChangeMonth(ByVal sender As Object, ByVal
e As MonthChangedEventArgs)
curMonth = e.NewDate.Date
LoadCacheForMonth(curMonth)
End Sub
Private Sub LoadCacheForMonth(ByVal curMonth As
DateTime)
' loads dataset for a
particular month into cache
Dim iMonth As Integer =
curMonth.Month
Dim iYear As Integer =
curMonth.Year
Dim cacheName As String =
iMonth.ToString & iYear.ToString & "Dates"
If Cache(cacheName) Is Nothing
Then
dtDates
= DatesForMonth(curMonth)
Cache.Insert(cacheName,
dtDates, Nothing, Cache.NoAbsoluteExpiration, _
TimeSpan.FromHours(3))
Else
dtDates
= CType(Cache(cacheName), DataTable)
End If
End Sub
Private Function DatesForMonth(ByVal Month As DateTime)
As DataTable
Dim cn As SqlConnection = New
SqlConnection(cnString)
Dim cmd As SqlDataAdapter = New
SqlDataAdapter("GetDatesForMonth", cn)
cmd.SelectCommand.CommandType =
CommandType.StoredProcedure
Dim dt As New DataTable()
Try
AppendSQLParameter(cmd.SelectCommand,
"@startDate", SqlDbType.DateTime, ParameterDirection.Input, 8, Month)
cmd.Fill(dt)
cmd.Dispose()
cn.Close()
Catch x As SqlException
lblMsg.Text
= x.Message
Finally
cn =
Nothing
End Try
Return dt
End Function
Protected Sub Calendar1_DayRender(ByVal sender As Object, ByVal e As
System.Web.UI.WebControls.DayRenderEventArgs) Handles Calendar1.DayRender
If noDatesForMonth Then Exit
Sub
' may have clicked selected
date again
If curMonth.Year = 1 Then
curMonth
= Convert.ToDateTime(Calendar1.SelectedDate.Month.ToString & "/1/" &
Calendar1.SelectedDate.Year.ToString)
End If
Dim matchRows() As DataRow
Dim myMonth As Integer =
curMonth.Month
LoadCacheForMonth(curMonth)
If ((dtDates Is Nothing) OrElse
(dtDates.Rows.Count = 0)) Then
' we
have no cached datatable or the datatable is empty
' no
dates to render for this month, shortcircuit any further processing
noDatesForMonth
= True
Exit
Sub
End If
Dim curDay As Integer
Dim dr As DataRow
Dim dayText As StringBuilder =
New StringBuilder()
Dim eventDay As Boolean
Dim c As TableCell = e.Cell
If e.Day.Date.Month = myMonth
Then
curDay
= e.Day.Date.Day
matchRows
= dtDates.Select("ShowDay=" & curDay.ToString)
For
Each dr In matchRows
eventDay
= True
dayText.Append("<br><b>")
dayText.Append(Convert.ToString(dr.Item("Venue")))
dayText.Append("</b>")
Next
If
eventDay Then
e.Cell.BackColor
= Color.Pink
c.Controls.Add(New
LiteralControl(dayText.ToString))
dayText
= New StringBuilder()
End If
End If
End Sub
End Class
|
Public Sub AppendSQLParameter(ByRef cmd As SqlCommand, _
ByVal
strParamName As String, _
ByVal
strDataType As SqlDbType, _
ByVal
strDirection As ParameterDirection, _
ByVal
lngSize As Integer, _
ByVal
varValue As Object)
Dim param As SqlParameter
If lngSize > 0 Then
param =
New SqlParameter(strParamName, strDataType, lngSize)
Else
param =
New SqlParameter(strParamName, strDataType)
End If
param.Direction = strDirection
If Not strDirection =
ParameterDirection.Output Then
If Not
((varValue Is System.DBNull.Value) Or (varValue Is Nothing)) Then
param.Value
= varValue
Else
param.Value
= System.DBNull.Value
End If
End If
cmd.Parameters.Add(param)
End Sub
|
|
A couple of points here. I am explicitly caching my datatables for each month
and year so that I only have to hit the database once in a while. These
datatables will stay cached for up to 3 hours, depending on how busy the
site is. You can obviously adjust these to your liking.
In my ChangeMonth sub, I simply get the new month from the
event args and populate my datatable for that month if necessary.
In my DatesForMonth sub, I'm executing my stored
procedure. The datahelper class that is referenced is simply a custom
class I use for data access. In this case I have a simplified parameter
builder that I use a lot.
My Calender1_DayRender is where the database information
populates my calender. If I have no information, I set the noDatesForMonth
boolean so I don't waste time as the rest of the calendar renders. This
is a simplistic example where I'm just writing out the Venue for the day and
changing the color of the Day cell to pink to highlight it. There are
lots of other things you can do.
Now let's look at how to give the user a way to look at the show details in Part
3
Tour Calendar With Detail Part 3
|
|