基于 DatePicker 的事件日历与 MVC3 和 AJAX






4.95/5 (8投票s)
基于 DatePicker 的事件日历与 MVC3 和 AJAX
引言
本文旨在概述解决一个常见问题的方案,即一个紧凑的事件日历,使用户能够快速查看即将发生的事件。这源于一个网站重写为 MVC3 的需求,该网站需要这样一个日历。我在这里分享我所发现的内容,因为我找不到一个完整的步骤指南。为了说明我想要实现的功能,这是我想要达成的效果:
- 有事件的日期是可选的,没有事件的日期则不可选。
- 如果用户将鼠标悬停在有事件的日期上,会在工具提示中显示事件的简短描述或标题。
- 如果点击了事件,将显示一个概述事件详情的页面。
另外,很重要的一点是说明本文不包含什么内容,即不包含如何存储事件。因此,本文也不提供添加事件的用户界面,仅提供假的视图事件功能。我想专注于创建日历及其与 MVC3 框架的交互过程,而不是数据存储和检索的具体细节。无论如何,事件的存储和存储数据的类型很可能是实现特定的。我将使用假的(stubbed)代码来模拟本文研究的真实应用程序,而不是实际的后端存储。
本文旨在提供一个实现日历的分步指南,但您需要具备一些 Javascript、jQuery 和 MVC3 的知识。特别是,您可能需要回顾一下通过 Javascript 进行的 AJAX 调用以及 MVC3 的 Action 如何处理这些调用,我会在看到关键点时加以强调。
源代码
源代码可从以下地址下载:
下载 Events_calendar.zip - 277.95 KB
项目设置
我使用了一个基本的 MVC3 项目作为示例代码,显然,如果您乐意添加自己的 Action 和 View,可以跳过这些步骤,直接进入“添加基本日期选择器”(不过您可能需要检查您的 jQuery 版本)。
如果您想跟着操作,请创建一个基本的 MVC3 项目:选择“新建项目”,然后选择“Web”,接着选择“MVC3 应用程序”,并选择“Empty”版本。我将在这篇文章中使用 Razor 语法作为我的站点和本文的语法,当然,其他引擎也会支持我们即将要做的,但 Razor 不仅是我的首选,而且似乎现在已成为普遍的选择。
VS2010 附带的 jQuery 和 jQuery-ui 库相当老旧,在撰写本文时,我已经将它们更新为最新版本,您可以在 https://jqueryui.jqueryjs.cn/download 找到它们。在该页面上,为了方便起见,我选择了所有选项。我将使用的版本是 *jquery-1.7.1.min.js* 和 *jquery-ui-1.8.17.custom.min.js*,如果您使用不同的版本,可能会得到不同的结果。请注意,文件名中的“.custom”部分是因为我选择的选项,它仍然只是标准的代码。顺便说一句,有一个很有用的在线工具可以帮助您为 jQueryUI 项目设置主题, 它可以在 https://jqueryui.jqueryjs.cn/themeroller/ 找到,您也可以同时下载 jQuery 库。
接下来,我添加了一个简单的页面来承载我们的日历。在 MVC3 中,要做到这一点:
- 在 MVC controllers 文件夹中添加一个 `HomeController` 类。它应该有一个 `Index` Action,该 Action 就像默认生成的那些方法一样,只需返回一个新的 `View`。
- 在 Views 文件夹中添加一个 Home 文件夹。
- 在刚创建的目录下添加一个 Index 视图。
- 在布局文件中替换了较新版本的 jQuery js 文件。
此时我做了一些有点奇怪的事情。*我从布局文件中删除了 jQuery 脚本标签*。这是为了尽量将相关的标记放在 Index.cshtml 文件中,这样您只需要引用一个文件。在实际应用中,需要做出正常的判断来决定每个脚本标签应该放在哪里。
添加基本日期选择器
现在我们需要将 jQuery 日期选择器添加到页面中。这是一个非常简单的过程:添加一个标签作为日历的容器,使其可以被 jQuery 选择(我添加了一个 `id` 为 datepicker 的 `div`),然后让 jQuery 在浏览器加载时生成日期选择器。
<div id="datepicker"></div>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.17.custom.min.js")" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#datepicker").datepicker();
});
</script>
从样式上看,结果并不令人惊艳,但至少我们已经开了个头。
接下来我们将为日历添加样式。在 \Content\Themes\Base 文件夹中有一个名为 jquery-ui.css 的默认样式,我们将使用它,因为它足以用于演示。如果您想自己创建主题,我推荐在导言中提到的 jQuery themeroller ,它能让创建一致的 CSS 样式更容易。回到主要内容,要添加默认主题,请将以下内容添加到 _layout.cshtml 的 `head` 标签中。
<link href="@Url.Content("~/Content/themes/base/jquery-ui.css")" rel="stylesheet" type="text/css" />
现在我们有了
好多了 :)
让日历做些事情
目前日历并没有告诉我们太多信息,只是显示了所选月份的有效日期。我们需要突出显示有事件的日期,并在鼠标悬停在日期上时提供可用事件的描述,所有这些实际上是相关 jQuery 中一个逻辑块。最后,我们需要处理用户选择日期的情况。
限制和突出显示可选日期。
首先,我们将定义一个名为 `GetCalendarEvents` 的控制器 Action,它将返回一个字典,其中键是日期编号,值是该日的事件标题。月份和年份将作为参数传递给此 Action。该字典将由 Action 以 JSON 格式返回,在客户端将它反序列化为一个“关联”数组。通常我会将字典的键类型设置为 `int`,因为我们将使用月份的日期编号,但我实际上使用了 `string`。这是因为 `string` 键可以在无需进一步编码的情况下被序列化,并且我们可以利用它到达客户端时与 `int` 无可区分的事实。为了方便起见,我将此方法放在 home controller 中,并且在本文中,我们将对所有事件方法都遵循此模式,但根据您的需求,您可能会发现一个专门的 events controller 更合适。代码如下:
[HttpPost]
public ActionResult GetEventsForMonth(int month, int year)
{
Dictionary<string, string> events = new Dictionary<string, string>();
for (int i = 1; i < 31; i += 2)
{
events.Add((month % 2 == 0 ? i + 1 : i).ToString(), string.Format("event on day {0}/{1}/{2}", i, month, year));
}
Thread.Sleep(500); //Simulate a database delay.
return Json(events);
}
如前所述,这是假的(stub)代码,它生成一个事件集,在偶数月份的偶数日期以及奇数月份的奇数日期。 `Thread.Sleep` 用于模拟数据库延迟,稍后您就会明白原因。请注意,我已用 `HttpPost` 属性装饰了该方法,这是客户端脚本将要调用的类型。
接下来我们需要编写一些 jQuery 代码来调用服务器方法。我们将执行以下操作:
- 创建一个空数组来存放调用的结果,我称之为 `calendarEvents`。
- 定义 AJAX 请求的详细信息(URL、类型和内容等)。
- 在 AJAX 调用中传递月份和年份参数。
- 如果调用成功,我们将 `calendarEvents` 设置为 AJAX 调用的结果;如果失败(即发生错误),我们将隐藏日历,以防止显示一个(具有误导性的空)日历。
- 最后,我们将刷新日历。
var calendarEvents = {};
function getEvents(month, year) {
$.ajax({
url: '@Url.Action("GetEventsForMonth")',
type: 'POST',
data: JSON.stringify({ month: month, year: year }),
dataType: 'json',
processdata: false,
contentType: 'application/json; charset=utf-8',
success: function (data) {
calendarEvents = data; },
error: function (xhr, ajaxOptions, thrownError) {
$("#datepicker").datepicker("hide"); },
complete: function (x, y) {
$("#datepicker").datepicker("refresh"); }
});
}
- 如果日期不在 `calendarEvents` 数组中,则返回 `[false,'']`。`false` 使日期不可选,空字符串清除 CSS 类。
- 如果日期确实存在于数组中,但工具提示的标题为空,则返回 `[true,'isActive']`,使该项突出显示且可选(但未指定工具提示)。
- 如果日期确实存在于数组中,并且有标题(在等效的 Dictionary 中是“值”),则除了第 2 项中的代码之外,还需要一个工具提示:`[true,'isActive', calendarEvents[day]]`。在此代码中,最后一个参数会将数组中的文本添加到工具提示中。
我们还需要向 `onChangeMonthYear` 事件添加一个回调函数,该函数将在月份或年份更改时调用我们上面定义的 AJAX 脚本,从而刷新日历。AJAX 代码在完成时强制刷新,所以我们在这里不必担心。最后,在添加日期选择器之后,我们需要调用我们的 AJAX 函数来执行初始填充。
$(function () {
$("#datepicker").datepicker({
beforeShowDay: function (date) {
var day = date.getDate();
if (day in calendarEvents) {
if (calendarEvents[day] == undefined) {
return [true, 'isActive'];
}
return [true, 'isActive', calendarEvents[day]];
}
return [false, ''];
},
onChangeMonthYear: function (year, month, inst) {
getEvents(month, year); }
});
var calendarDate = $("#datepicker").datepicker("getDate");
getEvents(calendarDate.getMonth() + 1, calendarDate.getFullYear());
});
唯一看起来有点奇怪的是 `calendarDate.getMonth() + 1`,这里需要加 1,因为在这种情况下月份是从 0 开始索引的。现在执行项目会渲染出:
对于奇数月份,以及对于偶数月份,正如我们根据假的(stub)代码所预期的那样。
我没有在截图中标出,但现在将鼠标悬停在日期上会弹出带有事件标题的工具提示,正如预期的那样。
两个小问题
在 Chrome 中测试时(我一直用来截屏的浏览器),这能正常工作,但旧版本的 IE 会出现问题。会引发一个错误:“*IE Microsoft JScript 运行时错误:'JSON' 未定义*”。这是 IE7 及更早版本的问题,它们在其 Javascript 实现中没有相关的 JSON 功能。当 IE8 以 IE7 兼容模式运行时也会出现这个问题,这可能在 VS2010 中发生。如果您安装了开发人员工具栏,可以通过将浏览器模式切换到 IE7 来模拟此行为。要修复这个问题,您只需要引用 JSON2 库,可以从 https://github.com/douglascrockford/JSON-js/blob/master/json2.js 下载,请注意,您可能希望将其缩小(minify)以用于生产环境。将其放入 scripts 文件夹并添加到项目中后,请将以下内容添加到标记中:
<!--[if lt IE 8]>
<script src="@Url.Content("~/Scripts/json2.js")" type="text/javascript"></script>
<![endif]-->
万一您以前没见过,“注释”(`