ASP.NET MVC 使用 Web API、Bootstrap Popover 和 jQuery UI 对话框实现工具提示






4.95/5 (6投票s)
作者正在分享一篇关于如何使用 Web API、Bootstrap Popover 和 jQuery UI 对话框为 HTML 元素创建帮助工具提示的文章。
目录
引言
在本文中,我将分享一些技巧,介绍如何动态地在表单元素(如文本框、标签、段落、按钮等)旁边添加一个帮助/工具提示/屏幕提示图标。当用户单击图标时,会显示一条消息/提示/文本,可以通过 jQuery UI 模态框或 Bootstrap popover 来显示,具体取决于设置。所有文本都将存储在一个中心位置,并通过 Web API 调用按需检索。这个想法的灵感来自于以下标准:
- 大多数工具提示会在焦点离开图标或表单元素后,或在用户开始输入时,或在几秒钟后关闭或消失。我想要一个单击后仍然打开的工具提示,以便用户可以花时间阅读消息,在必要时查找其他信息,然后将数据输入到表单元素中。用户可以使用 jQuery UI 模态框拖动或调整工具提示的大小,并通过按 X 按钮关闭工具提示。
- 工具提示内容应来自数据库,并通过 Web API 访问。我的目标之一是构建一个管理员界面,在一个位置管理工具提示元数据,而不是浏览每个页面。使用 Web API 是为了将来可以将公共元数据和服务暴露给其他应用程序。
- 易于集成。假设 Web API 已准备就绪且脚本已正确引用,您只需为表单元素添加一个属性,如果要使用 Bootstrap popover 功能,则添加两个属性。
最初的想法是仅使用 jQuery UI 创建解决方案,但后来我决定通过添加 Bootstrap Popover 来增添一些趣味。Popover的一个缺点是用户无法拖动或调整工具提示对话框的大小。开发人员可以自行决定是显示两者还是其中一个工具提示对话框模态框。
清单 1 和清单 2 分别展示了如何使用 jQuery UI 和 Bootstrap popover 添加工具提示图标的示例。
列表 1
<input type="text" class="form-control" data-yourtooltipctrl="lookupKey1" />
列表 2
<input type="text" class="form-control" data-yourtooltipctrl="lookupKey2" data-yourtooltiptype="1" />
实现
Web API
本文中的 Web API 非常直接,列在清单 3 中。为简化起见,API 返回结果是硬编码的,或者不是来自数据源。实际上,结果应源自数据源/存储库。在此演示中,Web 应用程序将使用 POST 方法按键获取工具提示元数据。使用 GET 方法也没有问题,目的是演示如何通过 POST 方法传递 AntiForgeryToken 和多个参数到 API。有关 AntiForgeryToken 如何实现以及由应用程序使用的更多详细信息,请参阅文章 Asp.net MVC Warning Banner using Bootstrap and AngularUI Bootstrap 。下面将有一个 JavaScript 部分,展示如何通过 AJAX POST 传递多个参数和 AntiForgeryToken。
列表 3
public class ToolTipController : BaseApiController
{
//this is just a sample, in reality, the data should be originated from a repository
IList<tooltip> toolTips = new List<tooltip>
{
new ToolTip { Id = 1, Key ="registerEmail", Title = "Email",
Description ="Enter your Email Address", LastUpdatedDate = DateTime.Now },
new ToolTip { Id = 2, Key ="registerPassword", Title = "Password policy",
Description ="some policy...", LastUpdatedDate = DateTime.Now}
};
…
[Route("{key}")]
public IHttpActionResult Get(string key)
{
var toolTip = toolTips.FirstOrDefault((p) => p.Key.ToLower() == key.ToLower());
if (toolTip == null)
{
return NotFound();
}
return Ok(toolTip);
}
[HttpPost]
[Route("GetWithPost")]
[AjaxValidateAntiForgeryToken]
public IHttpActionResult GetWithPost(Dummy dummy)
{
var toolTip = toolTips.FirstOrDefault((p) => p.Key == dummy.Key);
if (toolTip == null)
{
return NotFound();
}
return Ok(toolTip);
}
}
清单 4 显示了 BaseApiController 的内容。这个基类继承自 ApiController,并包含一个结构体。这里使用结构体而不是类,因为只有几个属性且生命周期较短。请随意稍后将其修改为类以满足您的需求。
列表 4
public class BaseApiController : ApiController
{
public struct Dummy
{
public string Key { get; set; }
public string Other { get; set; }
}
}
Web API - 启用跨域请求 (CORS)
从技术上讲,Web API 可以与 Web 应用程序一起托管,也可以单独托管。在此示例应用程序中,API 和应用程序是解耦的。也就是说,Web 和 API 应用程序可以托管在不同的子域或域上。根据设计,Web 浏览器的安全性同源策略会阻止网页向另一个域或子域发出 AJAX 请求。但是,可以通过启用跨域请求 (CORS) 来显式允许从 Web 到 API 应用程序的跨域请求来克服此问题。
清单 5 显示了 Global.asax 文件中启用 CORS 的代码。SetAllowOrigin 方法的目的是检查请求 URL 是否在白名单中,如果是,则将 Access-Control-Allow-Origin 标头值设置为 URL 值。此响应标头将向 Web 浏览器发出信号,表明请求的域/URL 被允许访问服务器上的资源。此外,在发送实际请求之前,Web 浏览器将使用 HTTP Options 方法向服务器发出“预检请求”。服务器的响应将告诉浏览器请求允许哪些方法和标头。在当前示例中,X-Requested-With 和 requestverificationtoken 已添加到标头中。前者由 AngularJS $http 服务函数 AJAX Request 使用,后者是 Web 应用程序的 antiforgerytoken 值。请参阅本文 Enabling Cross-Origin Requests in ASP.NET Web API 2 ,了解有关 CORS 和预检请求工作原理的更多信息。
列表 5
internal void SetAllowOrigin(string url)
{
//get the allow origin url from appsetting
string allowOrigin = System.Configuration.ConfigurationManager.AppSettings["AllowWebApiCallURL"];
if (allowOrigin.Split(';').Select(s=>s.Trim().ToLower()).Contains(url.ToLower()))
{
HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
//http://domain.com or * to allow all caller
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", url.ToLower());
}
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
SetAllowOrigin(HttpContext.Current.Request.Headers["Origin"] == null ?
string.Format("{0}://{1}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority)
: HttpContext.Current.Request.Headers["Origin"]);
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, PUT, POST");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, content-type, Accept, requestverificationtoken");
HttpContext.Current.Response.End();
}
}
Web API – 机器密钥
如前所述,在此示例中,Web 和 API 应用程序都托管在单独的域上。API 中的 POST 方法需要请求者的防伪令牌。为了让 Web API 解密令牌,两个应用程序都必须使用相同的机器密钥集。这是生成机器密钥的链接 http://www.developerfusion.com/tools/generatemachinekey/,以防您的应用程序需要一个。
您的简单工具提示脚本
页面加载时,客户端脚本将查找并遍历所有具有 data-yourtooltipctr 数据属性的 HTML 元素。在循环过程中,将创建一个图像按钮并放置在元素旁边。按钮中的 data-yourtooltipid 属性值将由 Web API 用于检索工具提示内容。其值从 data-yourtooltipctr 数据属性生成,因此请确保该值是唯一的。data-yourtooltiptype 属性的目的是标记工具提示是使用 jQuery 还是 Bootstrap 显示。如果该属性存在,工具提示将使用 Bootstrap Popover 显示,否则使用 jQuery UI Dialog 插件。有关更多详细信息,请参阅清单 6。可以通过修改图像源“/content/info_16x16.png”来替换图像。
列表 6
$('[data-yourtooltipctrl]').each(function (index) {
var toolTipType = '';
//get tooltiptype
if ($(this).data('yourtooltiptype')) {
toolTipType = $(this).data('yourtooltiptype');
}
var container = $("<a href='#' class='yourToolTipLink' data-yourtooltiptype='" + toolTipType
+ "' data-yourtooltipid='" + $(this).data('yourtooltipctrl')
+ "'><img alt='Click for detail' src='/content/info_16x16.png'/></a>" )
.css({
cursor: 'help' ,
'padding-left' : '2px'
});
$(this).css("display", "inline-block" );
if ($(this).is("label")) {
$(this).append(container);
}
else {
$(this).after(container);
}
});
清单 7 显示了由 yourToolTipLink 类选择器触发的图像按钮/工具提示图标点击事件的代码逻辑。最初,该逻辑将关闭所有先前打开的工具提示对话框。然后,它将利用 jQuery AJAX 函数 POST 到 Web API。API 接受两个参数,分别为 Key 和 Other。Key 值从 yourtooltipid 数据属性中检索,Other 参数包含一个占位符值。如前所述,Web API 将使用 Key 来检索工具提示内容。AJAX 函数还将利用 beforeSend 函数将 AntiForgerytoken 包含在请求标头中。
如果请求成功,逻辑将填充对话框内容。模态框将通过 jQuery UI Modal 或 Bootstrap Popover 显示,具体取决于 HTML 元素中是否指定了 data-yourtooltiptype 属性。此脚本中最具挑战性的部分是我们必须维护 API URL,而不能简单地使用相对路径,因为 API 托管在不同的域上。如果这是个问题,我的建议是修改此脚本以从全局变量读取 API URL。全局变量应从代码隐藏填充。这里有一个 文章,其中分享了一些关于如何将服务器端数据传递给 JavaScript 的示例。或者,在将应用程序部署到不同环境时,请记住更新 URL。
列表 7
$(".yourToolTipLink").on('click', function (e) {
e.stopPropagation();
e.preventDefault();
var o = $(this);
var toolTipTypeSpecified = $(this).attr('data-yourtooltiptype');
//close the dialog or tooltip, to make sure only one at a time
$(".yourToolTipLink").not(this).popover('hide');
if ($("#yourTooltipPanel").dialog('isOpen') === true) {
$("#yourTooltipPanel").not(this).dialog('close');
}
var Dummy = {
"Key": $(this).data('yourtooltipid'),
"Other": "dummy to show Posting multiple parameters to API"
};
jQuery.ajax({
type: "POST",
url: "https://:47503/api/tooltip/GetWithPost",
data: JSON.stringify(Dummy),
dataType: "json",
contentType: "application/json;charset=utf-8",
beforeSend: function (xhr) { xhr.setRequestHeader('RequestVerificationToken', $("#antiForgeryToken").val()); },
headers: {
Accept: "application/json;charset=utf-8",
"Content-Type": "application/json;charset=utf-8"
},
accepts: {
text: "application/json"
},
success: function (data) {
if (toolTipTypeSpecified) {
o.popover({
placement: 'right',
html: true,
trigger: 'manual',
title: data.Title + '<a href="#" class="close" data-dismiss="alert">×</a>',
content: '<div class="media"><div class="media-body"><p>' + data.Description + '</p></div></div>'
}).popover('toggle');
$('.popover').css({ 'width': '100%' });
}
else {
$("#yourTooltipPanel p").html(data.Description);
$("span.ui-dialog-title").text(data.Title);
$("#yourTooltipPanel").dialog("option", "position", {
my: "left top",
at: "left top",
of: o
}).dialog("open");
}
}
});
});
如何集成?
假设您的 Web API 已准备就绪并正在运行。将图 1 中指示的所有样式和 JavaScript 引用添加到 _Layout 页面。看起来很多,但您的应用程序应该已经包含了其中大部分,例如 jQuery 和 Bootstrap。_AntiforgeryToken.cshtml 部分视图包含一个隐藏字段来保存 antiforgerytoken。_WebToolTip.cshtml 部分视图包含一个 ID 为“yourTooltipPanel”的 div 元素,以及对 yourSimpleTooltip.js 文件的 JavaScript 引用。更新 JavaScript 中的 Web API URL 以指向您的 Web API。
图 1
在撰写本节时,我注意到 Antiforgerytoken 集成起来可能很麻烦,尤其是在 ASP.NET Webform、PHP、Classic ASP 等其他平台。在示例项目中,我创建了一个示例 HTML 页面来演示如何在没有令牌的情况下使用此工具提示脚本。基本上,我创建了另一个不关心令牌的 API 方法,并将脚本克隆到 yourSimpleTooltipNoToken.js 文件中。该文件排除了 jQuery AJAX post 中的 beforeSend 函数。我没有使其动态化以避免复杂化。请参阅图 2。
图 2
看点
在我之前的项目中,当向表单添加新元素时,我总是将位置设置为绝对,并动态计算 x 和 y 位置。假设我正在文本框元素旁边添加一个图标。此方法存在一个缺陷,因为当您调整屏幕大小时,图标位置将不同步,如图 3 所示。您必须刷新浏览器才能刷新图标位置,使其再次显示在文本框旁边。在此项目中,我使用 jQuery after 方法在表单元素之后插入图标。这样,在屏幕调整大小时,图标将始终与元素保持一致。
图 3
结论
我希望有人会发现这些信息有用,并让您的编程工作更轻松。如果您发现任何错误、不同意内容或想帮助改进本文,请给我留言,我会与您一起纠正。我建议下载演示并进行探索,以全面掌握概念,因为我可能遗漏了本文中的一些重要信息。如果您想帮助改进本文,请与我联系,并使用以下链接报告任何问题 https://github.com/bryiantan/SimpleLibrary/issues。
已测试于:Internet Explorer 11,10,9.0, Microsoft Edge 38, Firefox 52, Google Chrome 56.0, iPhone 6
历史
2017年7月3日 – 初始版本
2017年7月20日 – 移除了冗余标签
观看此脚本的实际演示
http://mvc.ysatech.com/Account/Register
下载
下载源代码 (https://github.com/bryiantan/SimpleLibrary)
资源
在 ASP.NET Web API 2 中启用跨域请求
如何通过单击页面上的任意位置来关闭 Twitter Bootstrap popover?
显示一个 popover 并隐藏其他 popover
为什么 ASP.NET MVC 的 Request.IsAjaxRequest 方法在 Angular 的 $http 调用时返回 false?