Introduction
Recently my company decided to host a standard calendar to show holidays on the employees portal, however the catch was that the calendar should display holidays / information / updates that the Human Resource wanted the employees to know, they did not want to use the Announcement Web Part.
Problem
We would like to display a conventional calendar on SharePoint Intranet as a WebPart, with the feature of showing customized information in the form of ToolTip for each day. This customized information is displayed from a SharePoint Custom List with English & Chinese text.
Pre-requisite
This Web Part is based on a custom SharePoint List with pre-defined columns for it to work. So provision a custom list in SharePoint with the following site columns:
Display Name | Internal Name | Type | Purpose |
Title | Title | Single Line Text | Display the holiday/announcement in English |
Holiday Date | Holiday_x0020_Date | DateTime (Only Date) | The day to render. |
TitleInChinese | TitleInChinese | Single Line Text | Display the holiday/announcement in Chinese |
Location | Location | Single Line Text | The location (in my case this was Hong Kong, Macau, China) |
Calendar
This web part is based on the standard System.Web.UI.WebControls.Calendar
control, so there is nothing fancy about this control. However in order to render the days with the list, we have to override the OnDayRender()
event.
public class MyCalander: System.Web.UI.WebControls.Calander
{
MyWebPart oParent;
public MyCalander(MyWebPart oCurrentParent)
{
this.oParent = oCurrentParent;
}
protected override void OnDayRender(TableCell cell, CalendarDay day)
{
try
{
cell.ToolTip = String.Empty;
cell.BorderWidth = new Unit(oParent.calBorderWidth);
if (day.IsToday)
{
cell.Font.Bold = true;
cell.BackColor = Color.FromKnownColor(oParent.calTodayBackColor);
cell.ForeColor = Color.FromKnownColor(oParent.calTodayForeColor);
}
if (day.IsWeekend)
{
cell.BackColor = Color.FromKnownColor(oParent.calWeekEndBackColor);
cell.ForeColor = Color.FromKnownColor(oParent.calWeekEndForeColor);
}
if (day.IsOtherMonth)
{
cell.BackColor = Color.FromKnownColor(oParent.calRestDayBackColor);
cell.ForeColor = Color.FromKnownColor(oParent.calRestDayForeColor);
}
if (this.HolidayScheduleListName == null ||
this.HolidayScheduleListName == String.Empty)
{
return;
}
SPWeb web = SPControl.GetContextWeb(this.Context);
SPSite siteCollection = SPContext.Current.Site;
SPList calendarList =
siteCollection.RootWeb.Lists[HolidayScheduleListName];
if (oParent.HolidayDateFilterSiteColumn == null ||
oParent.HolidayDateFilterSiteColumn == "")
oParent.HolidayDateFilterSiteColumn = "Holiday_x0020_Date";
if (!calendarList.Fields.ContainsField
(oParent.HolidayDateFilterSiteColumn))
{
CalendarWebPart.LogMessage
(oParent.HolidayDateFilterSiteColumn + " site column not found.");
return;
}
SPQuery query = new SPQuery();
query.ExpandRecurrence = true;
query.Query = "<Where><And><Eq><FieldRef Name='"
+oParent.HolidayDateFilterSiteColumn+"'/>
<Value Type='DateTime'>" + day.Date.ToString("yyyy-MM-dd") +
"T00:00:00Z</Value></Eq>";
query.Query += "<Eq><FieldRef Name='Location'/><Value Type='Text'>" +
oParent.strLocation + "</Value></Eq>";
query.Query += "</And></Where>";
try
{
SPListItemCollection spHolidays = calendarList.GetItems(query);
foreach (SPListItem oItem in spHolidays)
{
if (web.Language.ToString() == "1033") cell.ToolTip =
oItem["Title"].ToString();
else oItem["TitleInChinese"].ToString();
cell.BackColor = Color.FromKnownColor(oParent.calHolidayBackColor);
cell.ForeColor = Color.FromKnownColor(oParent.calHolidayForeColor);
}
}
catch (Exception innerException)
{
CalendarWebPart.LogMessage(innerException.Message);
}
}
catch (Exception e)
{
CalendarWebPart.LogMessage(e.Message);
throw e;
}
}
Lists Query
The critical part of the OnDayRender()
is the SPQuery
.
// SELECT Title, TitleInChinese From HolidayList WHERE
// Holiday_x0020_Date = Date_of_DayRendered
// This converts to the SPQuery as :
<Where>
<And>
<Eq>
<FieldRef Name='Holiday_x0020_Date'/><Value Type='DateTime'>
01/01/2010T00:00:00Z</Value>
</Eq>
<Eq>
<FieldRef Name='Location'/><Value Type='Text'>Hong Kong</Value>
</Eq>
</And>
</Where>
To build the dropdown with DISTINCT
values from the list, create a DataView
and convert the listitems
to DataTable
.
To get the DISTINCT
values from a list; use SPListItemCollection.GetDataTable
method and convert into a DataTable
using System.Data.DataView.ToTable()
method.
SPQuery query = new SPQuery();
query.Query = "<OrderBy><FieldRef Name='Location'/></OrderBy>"
query.ViewFields = "<FieldRef Name='Location' />";
DataTable tblUnique = new DataView
(calendarList.GetItems(query).GetDataTable()).ToTable(true, "Location");
Web Part Parameter : Color
The main concern was the look and feel of the entire Web Part, as it had to blend into the current theme. So the easy was to provide the webmaster with a String
object where he can enter the #ffffff
color code or to provide a drop-down with the color names.
This solution was to use the System.Drawing.KnownColor enum
and use the Color.FromKnownColor()
to convert the KnownColor
to Color
.
#region Calendar.Title.BackColor
private KnownColor TitleBackColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Title Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Title Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calTitleBackColor
{
get { return TitleBackColor; }
set { TitleBackColor = value; }
}
#endregion
Callback from DropDown
The painpoint is to generate a OnSelectionChanged()
trigger from the client side so that the webpart can be refreshed. The simplest way is to script out a Java function on client side to callback with the SelectedItemText:
protected override void OnPreRender(EventArgs e)
{
try
{
base.OnPreRender(e);
if (!Page.ClientScript.IsClientScriptBlockRegistered("OnChangeDD"))
{
String script = @"function dl_onchange() {
var dl=document.getElementById('" + ddlLocation.ClientID + @"');
var url = document.URL;
if (url.indexOf('?') > 0) url = url.substr(0, url.indexOf('?'));
document.location= url + '?Location='+dl.value;}";
Page.ClientScript.RegisterClientScriptBlock(typeof(Page),
"OnChangeDD", script, true);
}
if (HttpContext.Current.Request.QueryString.Count > 0)
{
this.strLocation = HttpContext.Current.Request.QueryString
["Location"];
}
this.getListItems();
cal.RefreshControl(this);
}
catch (Exception ex)
{
CalendarWebPart.LogMessage(ex.Message);
throw ex;
}
Putting It Together
There are total three classes in this WebPart
:
System.Web.UI.WebControls.WebParts.WebPart
,System.Web.UI.WebControls.DropDownList
System.Web.UI.WebControls.Calendar
public class CalendarWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
public String strLocation = String.Empty;
# region -- Source Filter Parameters
#region Holiday Schedule List Name e.g. Holiday Schedule
private string strHolidayScheduleListName = String.Empty;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("List Name: e.g. Holiday Schedule"),
WebDescription("Please input the holiday schedule list name.
(e.g. Holiday Schedule)"),
Category("Source"),
FriendlyName("List Name")]
public string HolidayScheduleListName
{
get { return strHolidayScheduleListName; }
set { strHolidayScheduleListName = value; }
}
#endregion
#region Holiday Lists Field Name e.g. Holiday_x0020_Date
private string strHolidayDateFilterSiteColumn = "Holiday_x0020_Date";
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Name of the Date Filter Site Column.
e.g. Holiday_x0020_Date."),
WebDescription("Exact site column name."),
Category("Source"),
FriendlyName("Date Column Name")]
public string HolidayDateFilterSiteColumn
{
get { return strHolidayDateFilterSiteColumn; }
set { strHolidayDateFilterSiteColumn = value; }
}
#endregion
#endregion
#region Calendar UI
#region -- Dimensions
private int TableHeight = 100;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Calendar Height"),
WebDescription("Enter a value"),
Category("Look & Feel"),
FriendlyName("Calendar Height"),
XmlElement(ElementName = "calTableHeight")]
public int calTableHeight
{
get { return TableHeight; }
set { TableHeight = value; }
}
private int TableWidth = 250;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Calendar Width"),
WebDescription("Enter a value"),
Category("Look & Feel"),
FriendlyName("Calendar Width"),
XmlElement(ElementName = "calTableWeidth")]
public int calTableWidth
{
get { return TableWidth; }
set { TableWidth = value; }
}
private int CellPadding = 5;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Cell Padding"),
WebDescription("Enter a value"),
Category("Look & Feel"),
FriendlyName("Cell Padding"),
XmlElement(ElementName = "calCellPadding")]
public int calCellPadding
{
get { return CellPadding; }
set { CellPadding = value; }
}
private int CellSpacing = 2;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Cell Spacing"),
WebDescription("Enter a value"),
Category("Look & Feel"),
FriendlyName("Cell Spacing"),
XmlElement(ElementName = "calCellSpacing")]
public int calCellSpacing
{
get { return CellSpacing; }
set { CellSpacing = value; }
}
#endregion
#region Calendar.BorderColor
private KnownColor BorderColor = KnownColor.White;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Border Color"),
WebDescription("Enter a Color value"),
Category("Look & Feel"),
FriendlyName("Border Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calBorderColor
{
get { return BorderColor; }
set { BorderColor = value; }
}
#endregion
#region Calendar.BorderWidth
private int intBorderWidth = 1;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Border Width"),
WebDescription("Enter the border width in unit"),
Category("Look & Feel"),
FriendlyName("Border Width"),
XmlElement(ElementName="calBorderWidth")]
public int calBorderWidth
{
get { return intBorderWidth; }
set { intBorderWidth = value; }
}
#endregion
#region Calendar.Title.BackColor
private KnownColor TitleBackColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Title Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Title Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calTitleBackColor
{
get { return TitleBackColor; }
set { TitleBackColor = value; }
}
#endregion
#region Calendar.Title.ForeColor
private KnownColor TitleForeColor = KnownColor.White;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Title Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Title Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calTitleForeColor
{
get { return TitleForeColor; }
set { TitleForeColor = value; }
}
#endregion
#region Calendar.Title.NextPrevBackColor
private KnownColor NextPrevBackColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Next Previous Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Next Previous Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calNextPrevBackColor
{
get { return NextPrevBackColor; }
set { NextPrevBackColor = value; }
}
#endregion
#region Calendar.Title.NextPrevForeColor
private KnownColor NextPrevForeColor = KnownColor.White;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Next Previous Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Next Previous Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calNextPrevForeColor
{
get { return NextPrevForeColor; }
set { NextPrevForeColor = value; }
}
#endregion
#region Calendar.Day.HeaderBackColor
private KnownColor DayHeaderBackColor = KnownColor.White;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Day Header Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Day Header Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calDayHeaderBackColor
{
get { return DayHeaderBackColor; }
set { DayHeaderBackColor = value; }
}
#endregion
#region Calendar.Day.HeaderForeColor
private KnownColor DayHeaderForeColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Day Header Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Day Header Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calDayHeaderForeColor
{
get { return DayHeaderForeColor; }
set { DayHeaderForeColor = value; }
}
#endregion
#region Calendar.Today.BackColor
private KnownColor TodayBackColor = KnownColor.Blue;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Today Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Today Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calTodayBackColor
{
get { return TodayBackColor; }
set { TodayBackColor = value; }
}
#endregion
#region Calendar.Today.ForeColor
private KnownColor TodayForeColor = KnownColor.Red;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Today Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Today Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calTodayForeColor
{
get { return TodayForeColor; }
set { TodayForeColor = value; }
}
#endregion
#region Calendar.Day.BackColor
private KnownColor DayBackColor = KnownColor.LightGray;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Day Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Day Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calDayBackColor
{
get { return DayBackColor; }
set { DayBackColor = value; }
}
#endregion
#region Calendar.Day.ForeColor
private KnownColor DayForeColor = KnownColor.Red;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Day Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Day Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calDayForeColor
{
get { return DayForeColor; }
set { DayForeColor = value; }
}
#endregion
#region Calendar.RestDay.BackColor
private KnownColor RestDayBackColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Other Days Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Other Days Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calRestDayBackColor
{
get { return RestDayBackColor; }
set { RestDayBackColor = value; }
}
#endregion
#region Calendar.RestDay.ForeColor
private KnownColor RestDayForeColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Other Days Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Other Days Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calRestDayForeColor
{
get { return RestDayForeColor; }
set { RestDayForeColor = value; }
}
#endregion
#region Calendar.Weekend.Backcolor
private KnownColor WeekEndBackColor = KnownColor.Black;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Weekend Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Weekend Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calWeekEndBackColor
{
get { return WeekEndBackColor; }
set { WeekEndBackColor = value; }
}
#endregion
#region Calendar.Weekend.Forecolor
private KnownColor WeekEndForeColor = KnownColor.White;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Weekend Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Weekend Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calWeekEndForeColor
{
get { return WeekEndForeColor; }
set { WeekEndForeColor = value; }
}
#endregion
#region Calendar.Holiday.Backcolor
private KnownColor HolidayBackColor = KnownColor.BurlyWood;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Holiday Back Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Holiday Back Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calHolidayBackColor
{
get { return HolidayBackColor; }
set { HolidayBackColor = value; }
}
#endregion
#region Calendar.Holiday.Forecolor
private KnownColor HolidayForeColor = KnownColor.White;
[Personalizable(PersonalizationScope.User),
WebBrowsable(true),
WebDisplayName("Holiday Fore Color"),
WebDescription("Enter a color value"),
Category("Look & Feel"),
FriendlyName("Holiday Fore Color"),
XmlElement(typeof(System.Drawing.KnownColor))]
public KnownColor calHolidayForeColor
{
get { return HolidayForeColor; }
set { HolidayForeColor = value; }
}
#endregion
#endregion
#region -- Private Objects
private DropDownList ddlLocation;
private MyCalendar cal;
private String strHref = String.Empty;
#endregion
public CalendarWebPart()
{
this.ExportMode = WebPartExportMode.All;
}
protected override void OnPreRender(EventArgs e)
{
try
{
base.OnPreRender(e);
if (!Page.ClientScript.IsClientScriptBlockRegistered("OnChangeDD"))
{
String script = @"function dl_onchange() {
var dl=document.getElementById('" + ddlLocation.ClientID + @"');
var url = document.URL;
if (url.indexOf('?') > 0) url = url.substr(0, url.indexOf('?'));
document.location= url + '?Location='+dl.value;}";
Page.ClientScript.RegisterClientScriptBlock
(typeof(Page), "OnChangeDD", script, true);
}
if (HttpContext.Current.Request.QueryString.Count > 0)
{
this.strLocation =
HttpContext.Current.Request.QueryString["Location"];
}
this.getListItems();
cal.RefreshControl(this);
}
catch (Exception ex)
{
CalendarWebPart.LogMessage(ex.Message);
throw ex;
}
}
protected override void CreateChildControls()
{
try
{
ddlLocation = new DropDownList() ;
ddlLocation.ID = "myDropDown";
ddlLocation.Attributes.Add("onchange", "dl_onchange();");
this.Controls.Add(ddlLocation);
cal = new MyCalendar();
this.cal.ID = "calCalendar1";
this.Controls.Add(cal);
}
catch (Exception e)
{
CalendarWebPart.LogMessage(e.Message);
throw e;
}
}
protected override void RenderContents(HtmlTextWriter writer)
{
try
{
EnsureChildControls();
ddlLocation.RenderControl(writer);
cal.RenderControl(writer);
}
catch (Exception e)
{
CalendarWebPart.LogMessage(e.Message);
throw e;
}
}
private void getListItems()
{
try
{
if (this.HolidayScheduleListName == null ||
this.HolidayScheduleListName == String.Empty)
{
ddlLocation.Items.Clear();
ddlLocation.Items.Add("Configure WebPart");
return;
}
SPSite siteCollection = SPContext.Current.Site;
SPList calendarList = siteCollection.RootWeb.Lists
[HolidayScheduleListName];
SPQuery query = new SPQuery();
query.ExpandRecurrence = true;
query.Query = "<orderby><fieldref name="Location"></orderby>";
query.ViewFields = "<fieldref name="Location">";
DataTable tblUnique = new DataView
(calendarList.GetItems(query).GetDataTable()).ToTable(true, "Location");
ddlLocation.Items.Clear();
foreach (DataRow row in tblUnique.Rows)
{
ddlLocation.Items.Add(row[0].ToString());
}
if (ddlLocation.Items.Count > 0)
{
if ((this.strLocation == null) ||
(this.strLocation == "")) this.strLocation =
ddlLocation.Items[0].Text;
else ddlLocation.Items.FindByValue(this.strLocation).Selected = true;
}
tblUnique = null;
}
catch (Exception ex)
{
CalendarWebPart.LogMessage(ex.Message);
throw ex;
}
}
public static void LogMessage(String errormsg)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
if (!EventLog.SourceExists("calCalendar"))
{
EventLog.CreateEventSource("calCalendar", "Application");
}
EventLog.WriteEntry("calCalendar", errormsg, EventLogEntryType.Error);
});
}
}
So there it is, a simple WebPart to display ToolTip Text from a SharePoint Custom Lists "Title" column.
History
- 17th September, 2010: Initial post