Click here to Skip to main content
11,437,713 members (30,949 online)
Click here to Skip to main content

DataCalendar

, 19 Feb 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
A data-driven, templated sub-class of the ASP.NET Calendar control

Introduction

This article presents a sub-class of the ASP.NET Calendar control, to function as a data-driven display calendar with template support for item layout. Though there are limitations to the approach of sub-classing Calendar, it is a useful alternative to creating such a control from scratch, as significant functionality can be inherited. The DataCalendar class is described with specific attention to data binding, templates, and styles.

Background

The ASP.NET Calendar control offers the ability to navigate a monthly calendar and select dates. But the standard Calendar control lacks support for data binding, so extra steps are required to display items from a database. The standard control does support a DayRender event, which provides a means to customize the display of individual days. I was interested in a control that functions more closely like a Repeater or DataList, with the ability to bind to a data source and use templates to control item display. Sub-classing the Calendar control seemed a good place to start.

To support data binding, a custom control will typically expose a DataSource property, override the Control.DataBind method, and construct a control hierarchy within CreateChildControls. This also usually involves maintaining ViewState for each of the child controls so explicit binding does not have to occur with each page postback. The Microsoft .NET SDK documentation offers source examples describing how to develop a data-bound control. The book Developing Microsoft ASP.NET Server Controls and Components by Nikhil Kothari and Vandana Datye (Microsoft Press, 2003) is also recommended for a full description of developing data-bound controls.

There are problems using the existing Calendar control this way. When a Repeater is bound to a data source, all items of that source are enumerated to generate child controls. With a Calendar, the display layout is dependent on a fixed set of days. Creating child controls for each item with ViewState maintained, particularly for those items that don't fall within the displayed month, doesn't seem practical. As it is, the Calendar control already creates TableRows and TableCells for child controls; if that were to be overridden, the month-based layout would be lost. If we can't take advantage of the layout functionality provided by the Calendar control, there isn't much point to inheriting from it.

Seeing these kinds of issues makes clear why the DayRender event is offered in the first place. DayRender is fired once for each day displayed during the Render event of the Calendar control. It becomes possible to simulate a data-bound control by exposing a DataSource property, then enumerating that data listing in an overridden version of the Calendar.OnDayRender method.

This approach has some important ramifications. The Render event comes relatively late in the control's lifecycle, and isn't usually where child controls are created. Child controls added at this stage will not fire events. We can't, for example, add a Button control in each day cell and code for its Click event - the Click event will not fire. On the other hand, content for display such as literal HTML or Label controls may be added without penalty. If we can tolerate a lack of event-firing controls rendered in day cells, the approach of sub-classing the Calendar control offers some terrific benefits: built-in styling capabilities, month-to-month navigation, day/week/month selection, a useful layout - all in all, there are enough benefits to this approach to make it worthwhile.

The DataCalendar Class

The DataCalendar class inherits from Calendar and implements INamingContainer. The constructor sets some defaults that make sense for a calendar whose primary purpose is to display entries as static content.

    public class DataCalendar : Calendar, INamingContainer
    {
        .
        .
        .
        // Constructor   
        public DataCalendar() : base()
        {
            // since this control will be used for displaying
            // events, set these properties as a default
            this.SelectionMode = CalendarSelectionMode.None;
            this.ShowGridLines = true;
        }
        .
        .
        .
    }

Simulating Data Binding

The essential properties for making this a data-driven control are defined as DataSource, DataMember, and DayField. DataSource is implemented here as either a DataSet or DataTable object, representing the listing of calendar items. If a DataSet object is supplied, then the DataMember property is implemented to allow the user to specify which table in the set to use. DayField is the name of the column in DataSource that represents the event date.

        private object _dataSource;
        private string _dataMember;
        private string _dayField;
        
        // Support either a DataSet or DataTable object
        // for the DataSource property
        public object DataSource {
            get {return _dataSource;}
            set 
            {
                if (value is DataTable || value is DataSet) 
                    _dataSource = value;
                else
                    throw new Exception("The DataSource property " +
                      "of the DataCalendar control must be a " +
                      "DataTable or DataSet object");
            }
        }
        
        // If a DataSet is supplied for DataSource,
        // use this property to determine which
        // DataTable within the DataSet should
        // be used; if DataMember is not supplied,
        // the first table in the DataSet will
        // be used.
        public string DataMember {
            get {return _dataMember;}
            set {_dataMember = value;}
        }
        
        
        // Specify the name of the field within
        // the source DataTable that contains
        // a DateTime value for displaying in the
        // calendar.
        public string DayField {
            get {return _dayField;}
            set {_dayField = value;}
        }

Often a data-bound control will support several types of objects for its DataSource property, such as those objects that implement the IEnumerable interface. For simplicity I chose to stay with a DataTable (or a table within a DataSet). The need for a DayField property to specify a date element within the data source also lends well to using a DataTable as opposed to other types of IEnumerable lists.

We'll use the Render method to inspect the DataSource property at run-time, determining if a DataSet or DataTable has been specified. We can then set up the private variable _dtSource to point to the appropriate DataTable object, and allow the base class Render method to execute.

        private DataTable _dtSource;

        protected override void Render(HtmlTextWriter html)
        {
            _dtSource = null;

            if (this.DataSource != null && this.DayField != null) 
            {
                // determine if the datasource is a DataSet or DataTable
                if (this.DataSource is DataTable) 
                    _dtSource = (DataTable) this.DataSource;                
                if (this.DataSource is DataSet)
                {
                    DataSet ds = (DataSet) this.DataSource;
                    if (this.DataMember == null || this.DataMember == "")
                        // if data member isn't supplied, 
                        // default to the first table
                        _dtSource = ds.Tables[0];
                    else
                        // if data member is supplied, use it
                        _dtSource = ds.Tables[this.DataMember];            
                }
                // throw an exception if there is a problem 
                // with the data source
                if (_dtSource == null)
                    throw new Exception(
                        "Error finding the DataSource.  Please check " +
                        " the DataSource and DataMember properties.");
            }                    
            // call the base Calendar's Render method, allowing
            // OnDayRender to be executed.
            base.Render(html);
        }

As DataCalendar inherits from Calendar, we override the OnDayRender method to customize the display of individual days. The argument cell represents the TableCell being rendered and acts as a placeholder for additional content. The specific date in question is derived from the CalendarDay argument day. We'll use the _dtSource private variable previously set by Render. With this we'll create a DataView object to filter the DataTable, extracting only those items that match the day argument based on the value of the DayField column. The code forces a "MM/dd/yyyy" date format when constructing the RowFilter as required for date comparisons in such expressions. The filter is also constructed to take into account the possibility of time values within the DayField column.

        protected override void OnDayRender(TableCell cell, CalendarDay day)
        {
            // _dtSource was already set by the Render method            
            if (_dtSource != null) 
            {
                // We have the data source as a DataTable now;                
                // filter the records in the DataTable for the given day;
                // force the date format to be MM/dd/yyyy
                // to ensure compatibility with RowFilter
                // date expression syntax (#date#).
                // Also, take the possibility of time
                // values into account by specifying
                // a date range, to include the full day
                DataView dv = new DataView(dtSource);
                dv.RowFilter = string.Format(
                   "{0} >= #{1}# and {0} < #{2}#", 
                   this.DayField, 
                   day.Date.ToString("MM/dd/yyyy"), 
                   day.Date.AddDays(1).ToString("MM/dd/yyyy")
                );
               
                // are there events on this day?
                if (dv.Count > 0) {
                    // there are events on this day;
                    .
                    .
                    .
                }
                else
                {
                    // no events this day;
                    .
                    .
                    .                       
                }
               
                 
            }           
           
            // call the base render method too
            base.OnDayRender(cell, day);
           
        }        

Supporting Templates

Another goal of the DataCalendar is to make use of templates. Templates allow for content layout to be defined within the HTML portion of an .aspx page by a page designer, rather than hard-coded into the class by a developer. A template contains HTML elements and ASP.NET controls, within which data binding expressions may be applied and resolved. The Repeater control for example supports, among others, a HeaderTemplate, ItemTemplate, and FooterTemplate. The DataCalendar control will support an ItemTemplate that works in the .aspx page like this:
    <dc:DataCalendar id="cal1" runat="server" width="100%"
                     DayField="EventDate" >

        <ItemTemplate>
            <b><%# Container.DataItem["EventTime"] %></b>
            <%# Container.DataItem["EventTitle"] %>
        </ItemTemplate>

     </dc:DataCalendar>

Data binding expressions <%# . . . %> should be resolved as the template is applied. To support this feature we need to define a container object for the calendar item. The syntax Container.DataItem["..."] refers to a DataItem property of this container object. Given that the source for the DataCalendar is a DataTable object, the DataItem property (a single item within the source) will logically be of type DataRow. The following code shows the class DataCalendarItem defined to serve as this container object:

    public class DataCalendarItem : Control, INamingContainer
    {

        private DataRow _dataItem;

        public DataCalendarItem(DataRow dr) {
            _dataItem = dr;
        }

        public DataRow DataItem {
           get {return _dataItem;}
           set {_dataItem = value;}
        }
    }

DataCalendarItem implements the INamingContainer interface, a requirement for template containers. This ensures that control names remain unique when templates are applied through multiple iterations of data items.

The next step is to define the ItemTemplate property in the DataCalendar class. This property is of type ITemplate, and is marked with the attribute TemplateContainer. The TemplateContainer attribute identifies which class will function as the container for an instance of the template. In our case, this will be the DataCalendarItem we just defined.

    public class DataCalendar : Calendar, INamingContainer
    {
        .
        .
        .
        private ITemplate _itemTemplate;
       
        [TemplateContainer(typeof(DataCalendarItem))]   
        public ITemplate ItemTemplate
        {
            get {return _itemTemplate; }
            set {_itemTemplate = value;}
        }
        .
        .
        .
    }

With these template definitions, we can now return to the OnDayRender method of the DataCalendar class. Each iteration through the data source gives us a single calendar item in the form of a DataRow. With that item we need to perform the following tasks:

  1. Create a container DataCalendarItem object, constructed with the item's DataRow
  2. Instantiate the ItemTemplate in the container (using the ITemplate.InstantiateIn() method)
  3. Execute the DataBind method of the DataCalendarItem object to resolve data binding expressions (this method is inherited from Control)
  4. Add the DataCalendarItem control to the TableCell that represents the given day

These tasks are handled through the private helper function SetupCalendarItem, which is executed from OnDayRender once for each calendar item:

    private void SetupCalendarItem(TableCell cell, DataRow r, ITemplate t)
    {
        // given a calendar cell and a datarow, set up the
        // templated item and resolve data binding syntax
        // in the template
        DataCalendarItem dti = new DataCalendarItem(r);
        t.InstantiateIn(dti);
        dti.DataBind();
        cell.Controls.Add(dti);           
    }    


    protected override void OnDayRender(TableCell cell, CalendarDay day)
    {
        if (_dtSource != null)
        {
            .
            .
            .
            // are there events on this day?
            if (dv.Count > 0) {
                // for each event on this day apply the
                // ItemTemplate, with data bound to the item's row
                // from the data source
                if (this.ItemTemplate != null)
                    for (int i=0; i<dv.Count; i++) {
                        SetupCalendarItem(cell, dv[i].Row,
                                          this.ItemTemplate);
                    }
            }
            else
            {
                // no events this day;
            }
        }           
        .
        .
        .
    }             

With support for an ItemTemplate in place, it is a simple matter to implement a NoEventsTemplate as well, also instantiated from the OnDayRender method. If a day in the data source has no calendar items, the NoEventsTemplate is applied.

            .
            .
            .
            else
            {
                // no events this day;
                if (this.NoEventsTemplate != null)
                    SetupCalendarItem(cell, null,
                                      this.NoEventsTemplate);
                   
            }
            .
            .
            .

Styles

As described before, a downside of the approach of sub-classing the Calendar control is that events from controls in our templates will not fire. A great upside to this approach however is the strong support the Calendar control offers for styles. Without any more code, we can make use of the properties DayStyle, TodayDayStyle, WeekdayDayStyle, OtherMonthDayStyle, and several others to customize the calendar's appearance.

In the DataCalendar class we will define one more data-relevant property of type TableCellStyle: DayWithEventsStyle. This property is applied in the OnDayRender event for each day that has data items. This allows the page designer the ability, for example, to set a different background color for days with events. The following shows the relevant code from DataCalendar to support DayWithEventsStyle:

    public class DataCalendar : Calendar, INamingContainer
    {
        .
        .
        .
        private TableItemStyle _dayWithEventsStyle;

        public TableItemStyle DayWithEventsStyle {
            get {return _dayWithEventsStyle;}
            set {_dayWithEventsStyle = value;}
        }
        .
        .
        .

        protected override void OnDayRender(TableCell cell, CalendarDay day)
        {
            if (_dtSource != null)
            {
                .
                .
                .
                // are there events on this day?
                if (dv.Count > 0) {
                    // there are events on this day; if indicated,
                    // apply the DayWithEventsStyle to the table cell
                    if (this.DayWithEventsStyle != null)
                        cell.ApplyStyle(this.DayWithEventsStyle);
                    .
                    .
                    .
                }
                else
                {
                    .
                    .
                    .                        
                }               
                 
            }           
            .
            .
            .
        }                  
   
    }

About the Examples

The examples in the sample project demonstrate different applications of the DataCalendar control. Since the displayed entries are not maintained through ViewState (as would be typical of a true data-bound control) other means of caching the calendar data are offered, including Session variables and the application Cache.

DataCalendar1.aspx

This example uses a DataTable object constructed in code, and shows a simple DataCalendar without much formatting.

DataCalendar2.aspx

This example demonstrates using data from an OLEDB data source, in this case an Access table. The <ItemTemplate> of this DataCalendar displays a hyperlink for each event, with a small image identifying the event category. The data source is cached in a Session variable.

DataCalendar3.aspx

The data source for this example is an XML document, using the XmlDataDocument class to get to the DataTable. This DataCalendar uses a little more formatting than the previous two. After loading, the data source is stored in the application Cache.

DataCalendar4.aspx

This example uses an OLEDB data source, without caching the results. Instead, the data source is queried with each postback, but a SQL Where clause limits the results to events for the displayed month.

DataCalendar5.aspx

This example shows the DataCalendar functioning more like a regular Calendar for selecting a date. No <ItemTemplate> is used, but the DayWithEventsStyle attribute is applied to highlight those days with events. A Repeater control is used to display events for the selected date.

Summary

The existing ASP.NET Calendar control is great for selecting dates but lacks data binding support. Sub-classing the Calendar control and overriding the OnDayRender method can simulate such support. Though controls that fire events can't be included in day cells using this approach, static content may be used as can the full contingent of Style properties inherited from the Calendar control. The DataCalendar class presented here may be used for displaying items from a DataTable, with layout customized by page designers through the use of its ItemTemplate and NoEventsTemplate properties. The DataCalendar class may itself serve as a base class for additional database-specific implementations.

History

  • 6 Feb 2004
- Updated to include support for DataSet objects and added the DataMember property; also updated OnDayRender() to better handle internationally formatted dates and dates with time values.
  • 3 Nov 2003
- Original posting

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mike Ellison

United States United States
I work for the University of Nevada, Las Vegas in the Office of Institutional Analysis and Planning. Among other things, our office is charged with the mission of deriving useful information in support of administrative decision-making from institutional data. Within the context of that mission, my office mates and I apply technology in the form of custom data processing applications, data extraction and analysis tools, reporting tools, relational databases, OLAP solutions, data warehousing, and data mining.

Visit my blog at MishaInTheCloud.com


Comments and Discussions

 
SuggestionMultiple day events Pin
Member 101360225-Jul-13 1:28
memberMember 101360225-Jul-13 1:28 
QuestionNeed code for getting calendars to show or hide events Pin
Member 100289054-May-13 9:22
memberMember 100289054-May-13 9:22 
QuestionI'm new Pin
Member 100276494-May-13 3:21
memberMember 100276494-May-13 3:21 
AnswerRe: I'm new Pin
Mike Ellison4-May-13 3:46
memberMike Ellison4-May-13 3:46 
QuestionSource code Pin
Boipelo17-Aug-12 0:02
memberBoipelo17-Aug-12 0:02 
AnswerRe: Source code Pin
Mike Ellison17-Aug-12 6:09
memberMike Ellison17-Aug-12 6:09 
GeneralMy vote of 5 Pin
jpratik22-Jun-12 4:39
memberjpratik22-Jun-12 4:39 
QuestionDataCalander for Windows Form Pin
djmak8-Apr-12 21:47
memberdjmak8-Apr-12 21:47 
AnswerRe: DataCalander for Windows Form Pin
Mike Ellison9-Apr-12 4:31
memberMike Ellison9-Apr-12 4:31 
QuestionAdd a button on day render = doesn't fire Pin
DeWaRs120620-Feb-12 10:06
memberDeWaRs120620-Feb-12 10:06 
AnswerRe: Add a button on day render = doesn't fire Pin
DeWaRs120620-Feb-12 10:25
memberDeWaRs120620-Feb-12 10:25 
GeneralMy vote of 5 Pin
manoj kumar choubey7-Feb-12 20:29
membermanoj kumar choubey7-Feb-12 20:29 
QuestionEvent on multiple day Pin
DeWaRs12066-Oct-11 23:19
memberDeWaRs12066-Oct-11 23:19 
AnswerRe: Event on multiple day Pin
Mike Ellison7-Oct-11 6:32
memberMike Ellison7-Oct-11 6:32 
GeneralRe: Event on multiple day Pin
DeWaRs120620-Feb-12 10:07
memberDeWaRs120620-Feb-12 10:07 
QuestionAm I missing something? downloaded demo and do not know how to start it. no default.aspx... Pin
androidiscool10-Sep-11 5:54
memberandroidiscool10-Sep-11 5:54 
AnswerRe: Am I missing something? downloaded demo and do not know how to start it. no default.aspx... Pin
Mike Ellison12-Sep-11 6:51
memberMike Ellison12-Sep-11 6:51 
Questionthank you ! Pin
wzoras19-Aug-11 3:05
memberwzoras19-Aug-11 3:05 
AnswerRe: thank you ! Pin
Mike Ellison26-Aug-11 12:52
memberMike Ellison26-Aug-11 12:52 
GeneralGreat! Pin
NorbyTheGeek9-Jun-11 5:46
memberNorbyTheGeek9-Jun-11 5:46 
QuestionIs it possible to define the Culture of the Calendar ? Pin
sot1925_19849-May-11 23:37
membersot1925_19849-May-11 23:37 
GeneralMy vote of 5 Pin
Member 374225211-Feb-11 2:31
memberMember 374225211-Feb-11 2:31 
GeneralDynamically add ItemTemplate Pin
Member 37422529-Feb-11 4:04
memberMember 37422529-Feb-11 4:04 
GeneralRe: Dynamically add ItemTemplate Pin
Mike Ellison9-Feb-11 6:59
memberMike Ellison9-Feb-11 6:59 
GeneralRe: Dynamically add ItemTemplate Pin
Member 37422529-Feb-11 23:03
memberMember 37422529-Feb-11 23:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150506.1 | Last Updated 20 Feb 2004
Article Copyright 2003 by Mike Ellison
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid