Asp.Net Ajax Control Toolkit中的Calendar是一个相当不错的日历控件。但是有些时候它却不能满足我们的要求,比如:这次客户要求不能选择当前日期以前的日期,只能点未来的日期。这个大家应该在酒店、航班等时效性要求比较强的网站上经常见到。之前的日期都加了个删除线,并且不能选择。
为了实现这个功能,又不想浪费这么好的Calendar控件功能。 怎么办呢?
原来,Asp.Net Ajax 为我们提供了扩展接口,方便我们对Asp.Net Ajax Control Toolkit的二次开发。
经过Google,在网上找到了一段原始代码,已经实现了,不能对之前的日期进行选择等功能。但是,在显示上,它却不能进行显示灰色,加上删除线。所以我进行的进一步的修改。代码如下(注:以下代码在VS2008 sp1 环境下编译通过):
---------------代码开始(Code Begin)----------------
CalendarCustom.cs代码如下:
--------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web.UI;
using AjaxControlToolkit;
[assembly: System.Web.UI.WebResource("Custom.CalendarExtender.js", "text/javascript")]
[assembly: System.Web.UI.WebResource("Custom.Calendar.css", "text/css", PerformSubstitution = true)]
namespace Custom
{
[ClientCssResource("Custom.Calendar.css")]
[ClientScriptResource("Custom.CalendarExtender", "Custom.CalendarExtender.js")]
[ToolboxData("<{0}:CalendarExtender runat=server></{0}:CalendarExtender>")]
public class CalendarExtender : AjaxControlToolkit.CalendarExtender
{
[DefaultValue(null)]
[ExtenderControlProperty]
[ClientPropertyName("lowerBoundDate")]
public virtual DateTime? LowerBoundDate
{
get { return GetPropertyValue("LowerBoundDate", (DateTime?)null); }
set { SetPropertyValue("LowerBoundDate", value); }
}
[DefaultValue(null)]
[ExtenderControlProperty]
[ClientPropertyName("upperBoundDate")]
public virtual DateTime? UpperBoundDate
{
get { return GetPropertyValue("UpperBoundDate", (DateTime?)null ); }
set { SetPropertyValue("UpperBoundDate", value); }
}
public IEnumerable<ScriptReference> GetScriptReferences()
{
return new ScriptReference[] { new ScriptReference("Custom.CalendarExtender.js", "Custom") };
}
}
}
----------------------------
CalendarExtender.js代码如下:
----------------------------
// Register the namespace for the control.
Type.registerNamespace('Custom');
Custom.CalendarExtender = function(element) {
/// <summary>
/// A behavior that attaches a calendar date selector to a textbox
/// </summmary>
/// <param name="element" type="Sys.UI.DomElement">The element to attach to</param>
Custom.CalendarExtender.initializeBase(this, [element]);
this._lowerBoundDate = null;
this._upperBoundDate = null;
}
Custom.CalendarExtender.prototype = {
initialize: function() {
/// <summary>
/// Initializes the components and parameters for this behavior
/// </summary>
Custom.CalendarExtender.callBaseMethod(this, "initialize");
},
dispose: function() {
/// <summary>
/// Disposes this behavior's resources
/// </summary>
Custom.CalendarExtender.callBaseMethod(this, "dispose");
},
get_lowerBoundDate: function() {
/// <summary>
/// The date currently visible in the calendar
/// </summary>
/// <value type="Date" />
if (this._lowerBoundDate != null) {
return this._lowerBoundDate;
}
},
set_lowerBoundDate: function(value) {
if (value && (String.isInstanceOfType(value)) && (value.length != 0)) {
value = new Date(value);
}
if (value) value = value.getDateOnly();
if (this._lowerBoundDate != value) {
this._lowerBoundDate = value;
this.raisePropertyChanged("lowerBoundDate");
}
},
get_upperBoundDate: function() {
/// <value type="Date">
/// The date to use for "Today"
/// </value>
if (this._upperBoundDate != null) {
return this._upperBoundDate;
}
},
set_upperBoundDate: function(value) {
if (value && (String.isInstanceOfType(value)) && (value.length != 0)) {
value = new Date(value);
}
if (value) value = value.getDateOnly();
if (this._upperBoundDate != value) {
this._upperBoundDate = value;
this.raisePropertyChanged("upperBoundDate");
}
},
_performLayout: function() {
/// <summmary>
/// Updates the various views of the calendar to match the current selected and visible dates
/// </summary>
var elt = this.get_element();
if (!elt) return;
if (!this.get_isInitialized()) return;
if (!this._isOpen) return;
var dtf = Sys.CultureInfo.CurrentCulture.dateTimeFormat;
var selectedDate = this.get_selectedDate();
var visibleDate = this._getEffectiveVisibleDate();
var todaysDate = this.get_todaysDate();
switch (this._mode) {
case "days":
var firstDayOfWeek = this._getFirstDayOfWeek();
var daysToBacktrack = visibleDate.getDay() - firstDayOfWeek;
if (daysToBacktrack <= 0)
daysToBacktrack += 7;
var startDate = new Date(visibleDate.getFullYear(), visibleDate.getMonth(), visibleDate.getDate() - daysToBacktrack, this._hourOffsetForDst);
var currentDate = startDate;
for (var i = 0; i < 7; i++) {
var dayCell = this._daysTableHeaderRow.cells[i].firstChild;
if (dayCell.firstChild) {
dayCell.removeChild(dayCell.firstChild);
}
dayCell.appendChild(document.createTextNode(dtf.ShortestDayNames[(i + firstDayOfWeek) % 7]));
}
for (var week = 0; week < 6; week++) {
var weekRow = this._daysBody.rows[week];
for (var dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
var dayCell = weekRow.cells[dayOfWeek].firstChild;
if (dayCell.firstChild) {
dayCell.removeChild(dayCell.firstChild);
}
dayCell.appendChild(document.createTextNode(currentDate.getDate()));
dayCell.title = currentDate.localeFormat("D");
dayCell.date = currentDate;
//modify by arden 2008.8.21
$common.removeCssClasses(dayCell.parentNode, ["ajax__calendar_other", "ajax__calendar_disabled", "ajax__calendar_active", "custom__calendar_lower"]);
if (this._lowerBoundDate == null && this._upperBoundDate == null ) {
Sys.UI.DomElement.addCssClass(dayCell.parentNode, this._getCssClass(dayCell.date, 'd'));
}
else if ((this._upperBoundDate != null && currentDate > this._upperBoundDate) ||
(this._lowerBoundDate != null && currentDate < this._lowerBoundDate))
{
Sys.UI.DomElement.addCssClass(dayCell.parentNode, "custom__calendar_lower");
}
//modify end
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1, this._hourOffsetForDst);
}
}
this._prevArrow.date = new Date(visibleDate.getFullYear(), visibleDate.getMonth() - 1, 1, this._hourOffsetForDst);
this._nextArrow.date = new Date(visibleDate.getFullYear(), visibleDate.getMonth() + 1, 1, this._hourOffsetForDst);
if (this._title.firstChild) {
this._title.removeChild(this._title.firstChild);
}
this._title.appendChild(document.createTextNode(visibleDate.localeFormat("MMMM, yyyy")));
this._title.date = visibleDate;
break;
case "months":
for (var i = 0; i < this._monthsBody.rows.length; i++) {
var row = this._monthsBody.rows[i];
for (var j = 0; j < row.cells.length; j++) {
var cell = row.cells[j].firstChild;
cell.date = new Date(visibleDate.getFullYear(), cell.month, 1, this._hourOffsetForDst);
cell.title = cell.date.localeFormat("Y");
$common.removeCssClasses(cell.parentNode, ["ajax__calendar_other", "ajax__calendar_disabled", "ajax__calendar_active"]);
Sys.UI.DomElement.addCssClass(cell.parentNode, this._getCssClass(cell.date, 'M'));
}
}
if (this._title.firstChild) {
this._title.removeChild(this._title.firstChild);
}
this._title.appendChild(document.createTextNode(visibleDate.localeFormat("yyyy")));
this._title.date = visibleDate;
this._prevArrow.date = new Date(visibleDate.getFullYear() - 1, 0, 1, this._hourOffsetForDst);
this._nextArrow.date = new Date(visibleDate.getFullYear() + 1, 0, 1, this._hourOffsetForDst);
break;
case "years":
var minYear = (Math.floor(visibleDate.getFullYear() / 10) * 10);
for (var i = 0; i < this._yearsBody.rows.length; i++) {
var row = this._yearsBody.rows[i];
for (var j = 0; j < row.cells.length; j++) {
var cell = row.cells[j].firstChild;
cell.date = new Date(minYear + cell.year, 0, 1, this._hourOffsetForDst);
if (cell.firstChild) {
cell.removeChild(cell.lastChild);
} else {
cell.appendChild(document.createElement("br"));
}
cell.appendChild(document.createTextNode(minYear + cell.year));
$common.removeCssClasses(cell.parentNode, ["ajax__calendar_other", "ajax__calendar_disabled", "ajax__calendar_active"]);
Sys.UI.DomElement.addCssClass(cell.parentNode, this._getCssClass(cell.date, 'y'));
}
}
if (this._title.firstChild) {
this._title.removeChild(this._title.firstChild);
}
this._title.appendChild(document.createTextNode(minYear.toString() + "-" + (minYear + 9).toString()));
this._title.date = visibleDate;
this._prevArrow.date = new Date(minYear - 10, 0, 1, this._hourOffsetForDst);
this._nextArrow.date = new Date(minYear + 10, 0, 1, this._hourOffsetForDst);
break;
}
if (this._today.firstChild) {
this._today.removeChild(this._today.firstChild);
}
this._today.appendChild(document.createTextNode(String.format(AjaxControlToolkit.Resources.Calendar_Today, todaysDate.localeFormat("MMMM d, yyyy"))));
this._today.date = todaysDate;
},
_isOutOfBounds: function(date, part) {
/// <summary>
/// Gets whether the supplied date is out of the lower / upper bound date limits
/// </summary>
/// <param name="date" type="Date">The date to match</param>
/// <param name="part" type="String">The most significant part of the date to test</param>
/// <returns type="Boolean" />
switch (part) {
case 'd':
if (this._lowerBoundDate && date < this._lowerBoundDate)
return true;
else if (this._upperBoundDate && date > this._upperBoundDate)
return true;
case 'M':
if (this._lowerBoundDate && (date.getMonth() < this._lowerBoundDate.getMonth() ||
date.getFullYear() < this._lowerBoundDate.getFullYear()))
return true;
else if (this._upperBoundDate && (date.getMonth() > this._upperBoundDate.getMonth() ||
date.getFullYear() > this._upperBoundDate.getFullYear()))
return true;
case 'y':
if (this._lowerBoundDate && date.getFullYear() < this._lowerBoundDate.getFullYear())
return true;
else if (this._upperBoundDate && date.getFullYear() > this._upperBoundDate.getFullYear())
return true;
}
return false;
},
_getCssClass: function(date, part) {
/// <summary>
/// Gets the cssClass to apply to a cell based on a supplied date
/// </summary>
/// <param name="date" type="Date">The date to match</param>
/// <param name="part" type="String">The most significant part of the date to test</param>
/// <returns type="String" />
if (this._isSelected(date, part)) {
return "ajax__calendar_active";
} else if (this._isOutOfBounds(date, part)) {
return "ajax__calendar_disabled";
} else if (this._isOther(date, part)) {
return "ajax__calendar_other";
} else {
return "";
}
},
_cell_onclick: function(e) {
/// <summary>
/// Handles the click event of a cell
/// </summary>
/// <param name="e" type="Sys.UI.DomEvent">The arguments for the event</param>
e.stopPropagation();
e.preventDefault();
if (!this._enabled) return;
var target = e.target;
var visibleDate = this._getEffectiveVisibleDate();
Sys.UI.DomElement.removeCssClass(target.parentNode, "ajax__calendar_hover");
switch (target.mode) {
case "prev":
case "next":
this._switchMonth(target.date);
break;
case "title":
switch (this._mode) {
case "days": this._switchMode("months"); break;
case "months": this._switchMode("years"); break;
}
break;
case "month":
if (!this._isOutOfBounds(target.date, 'M')) {
if (target.month == visibleDate.getMonth()) {
this._switchMode("days");
} else {
this._visibleDate = target.date;
this._switchMode("days");
}
}
break;
case "year":
if (!this._isOutOfBounds(target.date, 'y')) {
if (target.date.getFullYear() == visibleDate.getFullYear()) {
this._switchMode("months");
} else {
this._visibleDate = target.date;
this._switchMode("months");
}
}
break;
case "day":
if (!this._isOutOfBounds(target.date, 'd')) {
this.set_selectedDate(target.date);
this._switchMonth(target.date);
this._blur.post(true);
this.raiseDateSelectionChanged();
}
break;
case "today":
this.set_selectedDate(target.date);
this._switchMonth(target.date);
this._blur.post(true);
this.raiseDateSelectionChanged();
break;
}
}
}
Custom.CalendarExtender.registerClass("Custom.CalendarExtender", AjaxControlToolkit.CalendarBehavior);
PS: 如果AjaxControlToolkit升级到了40412verstion, 默认的客户端命名空间要改为Sys.Extended.UI,不然就会报"AjaxControlTookit is undefined"错误。
---------------------
Calendar.css代码如下:
---------------------
.custom__calendar_lower{color: #C0C0C0;text-decoration: line-through;}
---------------代码结束(Code End)----------------
注:在CalendarCustom.cs文件中加红显示的,是我这次解决这个问题花费时间最长的地方。一不小心就会报错了,出现问题时可查看以下内容
http://blogs.msdn.com/irenak/archive/2007/04/17/sysk-330-how-to-use-an-embedded-resource-from-a-sub-folder.aspx
-------------------摘录开始(Begin)----------------
SYSK 330: How To Use An Embedded Resource From A Sub-Folder
If you’re creating a custom client-side control by deriving from System.Web.UI.IScriptControl, you may want to embed the .js files into your assembly instead of having to deploy them to every web site that uses those controls (this is especially valuable if you’re developing a control library). All you need to do is to:
- Click on the .js file in the Solution Explore to select it, and then set Build Action (in the properties window) to Embedded Resource.
- Add the following attribute to your control code-behind file (e.g. MyControl.cs):
[assembly: System.Web.UI.WebResource("ControlLibrary.MyControl.js", "text/javascript")]
- In GetScriptReferences, instead of setting the Path property, use the parameterized constructor as follows:
public IEnumerable<ScriptReference> GetScriptReferences()
{
return new ScriptReference[] { new ScriptReference("ControlLibrary.MyControl.js", "ControlLibrary") };
}
However, if you place your JavaScript files in a subfolder (e.g. /ControlLibrary/Scripts/MyControl.js), then you will have to add the folder name to the dotted resource name above, e.g. ControlLibrary.Scripts.MyControl.js.
If you don’t add the folder name, you’ll probably see a run-time error message like this:
System.InvalidOperationException: Assembly ControlLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' contains a Web resource with name 'ControlLibrary.MyControl.js', but does not contain an embedded resource with name 'ControlLibrary.MyControl.js'.
-------------------摘录结束(End)----------------
页面代码:
<%@ Register Assembly="Custom" Namespace="Custom" TagPrefix="cc1" %>
<cc1:CalendarExtender LowerBoundDate="2010-7-18" UpperBoundDate="2010-12-30" ID="CalendarExtender1" runat="server" TargetControlID="Date1" />
运行效果: