ASP.NET 类似 Outlook 的时间字段






4.49/5 (15投票s)
一篇关于构建一个模仿 Microsoft Outlook 中时间字段行为的文章。

引言
我公司 IT 部门几乎完全是服务型的,主要涉及现场技术人员。我们有一个内部的电子商务门户,用于跟踪为客户提供的服务。我们技术人员抱怨最多的就是输入服务电话的开始和结束时间有多么烦人。我提供了三个下拉列表来输入每个时间字段:一个用于小时,一个用于分钟,一个用于上午/下午。我已经受够了抱怨;是时候做点什么了。我在网上找到的几乎所有时间选择器要么基于相同的概念,要么是一个带有掩码输入的文本框。我觉得这些同样令人讨厌,因为在字段之间切换有时很麻烦,而且输入通常过于受限。
我最喜欢的 Outlook 功能之一是用于约会和任务的时间选择器。它是一个文本框,在失去焦点时会应用逻辑。它可以接受几乎任何您输入的内容,并以 **hh:mm tt** 格式(例如 3:45 PM)计算时间。我决定复制这种行为,并将其封装在一个 ASP.NET 2.0 Web 控件中。标准的 TextBox
控件提供了我完成任务所需的 99%,所以我从这个控件派生了我的类并开始编码!
背景
我们决定该控件必须接受以下任何一种输入
- 3:45 PM
- 1:45a
- 230a
- 545p
- 1843
- 23
- 21:30
我尝试了两次才弄好。我第一次尝试解析输入以查找冒号的位置,分别将两边读取为小时和分钟,然后搜索 'a' 或 'p' 来指示一天中的时间。然而,当输入不完全符合预期时,例如使用了小数点时,这被证明很麻烦。
我的最终实现通过将文本输入分为两个组件来处理这个问题:一个数字组件和一个文本组件。我通过以下客户端 JavaScript 来实现这一点(完整的 JavaScript 源代码可以在 App_GlobalResources\OutlookTimeField.txt 中找到)
客户端实现
function UpdateTime(sender)
{
...
var numericPart = '';
var characterPart = '';
var i;
var current;
// Break the text input into numeric and
// character parts for easier parsing
for(i = 0; i < text.length; i++)
{
current = text.charAt(i);
if(IsNumeric(current))
numericPart = numericPart + current;
if(IsCharacter(current))
characterPart = characterPart + current;
}
...
}
function IsNumeric(text)
{
var validChars = '0123456789';
return (validChars.indexOf(text) > -1)
}
function IsCharacter(text)
{
var validChars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
return (validChars.indexOf(text) > -1)
}
在文本被分割后,弄清楚用户意图相对简单。首先,如果 characterPart
包含 A 或 a,则将其视为 AM(除非小时大于 12)。否则,默认为 PM。其次,根据 numericPart
中输入的字符数,根据以下算法分割小时和分钟
...
if(numericPart.length >= 4)
{
hour = numericPart.substring(0, 2);
minute = numericPart.substring(2, 4);
}
else if(numericPart.length == 3)
{
hour = numericPart.substring(0, 1);
minute = numericPart.substring(1, 3);
}
else if(numericPart.length == 2)
{
hour = numericPart.substring(0, 2);
minute = '00';
}
else if(numericPart.length == 1)
{
hour = numericPart.substring(0, 1);
minute = '00';
}
else
{
// Just use the current hour
var d = new Date();
hour = d.getHours();
minute = '00';
}
...
接下来,应用一些 24 小时制逻辑
...
if(hour > 12)
{
if(hour <= 24)
{
hour -= 12;
dayPart = 'PM';
}
else
{
// If the hour is still > 12 then the
// user has inputed something that doesn't
// exist, so just use current hour
hour = (new Date()).getHours();
if(hour > 12)
{
hour -= 12;
dayPart = 'PM';
}
else
{
dayPart = 'AM';
}
}
}
if(hour == 0)
{
hour = 12;
dayPart = 'AM';
}
...
在客户端,剩下要做的就是更新发送文本框的值:sender.value = hour + ':' + minute + ' ' + dayPart;
服务器端实现
服务器端代码确实没有什么特别之处。首先,重写 Render
方法以在 TextBox
中添加 onBlur
事件
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(BLUR_ATTRIBUTE, "UpdateTime(this);");
base.Render(writer);
}
其次,重写 OnPreRender
方法以注入客户端脚本
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (!Page.ClientScript.IsClientScriptBlockRegistered(this.GetType(),
SCRIPT_KEY))
{
Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
SCRIPT_KEY, Resources.ControlResources.OutlookTimeField, true);
}
}
最后,创建一个新属性,该属性返回一个具有相应时间的 TimeSpan
结构
public TimeSpan Time
{
get
{
if (string.IsNullOrEmpty(Text))
return TimeSpan.Zero;
else
return TimeSpanHelper.GetTimeSpan(Text);
}
set
{
Text = TimeSpanHelper.ConvertTimeSpanToString(value);
}
}
我将不详细介绍转换为 TimeSpan
结构的代码,因为它相对简单。您可以在 App_Code\TimeSpanHelper.cs 中找到我的实现。
Using the Code
它的用法与其他 Web 控件基本相同,但为了完整起见
...
<%@ Register TagPrefix="mbc" Namespace="Mbccs.WebControls" %>
...
<mbc:OutlookTimeField runat="server" ID="startTime" AutoCompleteType="None"/>
...
关注点
- 将
OutlookTimeField
的AutoCompleteType
属性设置为None
,以防止任何浏览器干扰。 - 我没有将此 Web 控件实现为独立的 DLL,原因是为了简单起见,而且有很多关于这样做的文章。
- 如果输入了无效数据(控件未重新格式化)并尝试回发,我将故意抛出
FormatException
(这不是错误)。如果您想阻止这种情况,您应该- 添加
RegularExpressionValidator
,使用 App_Code\TimeSpanHelper.cs 中找到的正则表达式,或 - 捕获异常并应用一些自定义逻辑,也许使用默认日期或当前日期。
- 添加
致谢
感谢 Alfredo Pinto 提供了 VB.NET 版本。
历史
- 2005 年 12 月 11 日 -- 发布原始版本
- 2005 年 12 月 23 日 -- 文章更新
- 2006 年 1 月 9 日 -- 文章迁移
- 2007 年 10 月 10 日 -- 添加 VB.NET 源代码下载和致谢部分