动态可更新且支持 AJAX 数据的 jQuery 网页对话框
本文介绍的是可动态更新且支持 AJAX 数据绑定的 JQuery 网页对话框。
引言
在我的 Web 应用程序项目中,有许多多层对话框。我需要在现有对话框中动态更新组件,以用于工作流程中的后续任务。我还希望通过 AJAX 调用将 ASP.NET MVC 的部分视图和相应数据加载到对话框中,并尽量减少调用者的代码行数。由于找不到任何库或商业工具能满足我的所有需求,因此我根据这些要求和功能创建了自己的 JQuery 插件,名为 jqsDialog
。
- 易于使用和自定义。
- 对话框可以打开到任何深度级别,并可以单独关闭或同时关闭多个级别。
- 任何现有的对话框组件都可以动态更新,因此同一个对话框实例可以按顺序渲染不同的窗口。
- 内置函数,用于从 AJAX 调用加载任何部分视图和分离的 JSON 数据。
- 使用现有或新的对话框实例,为任何处理延迟显示进度条窗口。
- 包含模态、非模态以及点击外部关闭对话框的选项。
- Bootstrap 用于整体主题和样式,可通过自定义样式表进行修改。
- 具备所有常见的对话框行为,因此一个工具可以满足网站所有页面上的所有需求。
运行演示项目
下载的源码包含一个 ASP.NET MVC 5.0 网站,用于详细演示如何使用 jqsDialog
插件。该项目可以直接使用 Visual Studio 2012/2013 打开。在重新生成过程中,项目应会自动下载引用的 .NET 相关库。由于项目中包含了 JQuery、JQuery UI、Bootstrap 和 KnockoutJS 的所有依赖 JS 和 CSS 库,因此模态对话框窗口和数据加载应能在 Windows 7 至 8.1 上的 IE 8.0 - 10.0、Mozilla Firefox 25.0 和 Google Chrome 33.0 浏览器中正常工作。请参阅演示应用程序使用的 JS 和 CSS 文件列表(位于 App_Start\BundleConfig.cs)。如果您运行的是其他浏览器类型或版本,您可能需要调整这些依赖库的版本或内容。
创建对话框的基础函数
popupDialog
函数构建了对话框的核心结构和组件。该函数通常在插件内部被处理通用对话框、加载数据和进度条窗口的其他函数调用,尽管它也可以直接在插件外部使用以下语法调用:
[returned dialog instance] = $("[selector]").jqsDialog("popupDialog", [argObject]);
以下是一个通过输入参数对象及其属性调用该函数的示例。默认值以方括号显示。
var popup = $("[selector]").jqsDialog("popupDialog", {
message: "(text)",
id: ["(GUID)"],
width: [250],
maxHeight: [$(window).height() - 120],
title: ["Information"],
titleTag: ["h4"],
icon: [undefined],
actionButtonLabel: [undefined],
actionButtonType: [undefined or "button" if actionButtonLabel has value],
closeButtonLabel: ["OK"],
mainCssClass: ["jqs-dialog-main"],
headerCssClass: ["jqs-dialog-header"],
bodyCssClass: ["jqs-dialog-body"],
footerCssClass: ["jqs-dialog-footer"],
animation: [undefined],
clickOutside: [false],
modal: [true],
fadeIn: [true],
parentResetClickOutside: [undefined],
parentsToClose: [undefined],
stopClose: [false]
});
我们可以通过一个消息文本作为唯一必需的输入参数来调用此函数,以创建一个简单的警告类型的对话框。请注意,基础函数只能创建对话框,如果直接调用,则无法更新。已发布的对话框实例成员或包装函数将用于动态更新现有对话框实例。
虚拟对话框容器和已发布成员
调用 popupDialog
函数时,会创建一个名为虚拟对话框容器 (VDC) 的不可见包装器,并附加可见的对话框。它绑定到顶层元素 document.body
(对于模态对话框)或 JQuery 选择器(对于非模态对话框)。
//Set virtual dialog container
var vdc = $("<table><tr><td align='center'>");
if (args.modal) {
//Append virtual dialog container to document.body
$("body").append(vdc);
}
else {
//Append virtual dialog container to selector
container.append(vdc);
}
VDC 还包含元素、函数和事件,这些可以在对话框创建后从插件外部的代码调用。这些已发布的成员是实现动态更新功能的基础。调用任何对话框实例成员都很简单。例如,使用以下代码行将现有对话框实例 popup
的 closeButton
标签更改为 "Cancel"
:
popup.setCloseBtn("Cancel");
插件的每个主要函数上方都有完整的规范部分。您可以在 jqsDialog.js 文件中从 popupDialog
函数中查看这些已发布对话框实例成员的详细信息。
通用对话框函数
尽管通过直接调用已发布的对话框实例成员来动态更新现有对话框的组件是可能的,但这会导致调用者编写更多的代码。为了进一步简化调用代码,插件提供了 showCommonPopup
包装函数,用于创建和更新警告/消息和确认类型的对话框。该函数有重载版本。我们可以为简单的单按钮模态对话框指定有限的单个参数,或者为具有更多功能的任何对话框指定参数对象。
showCommonPopup
函数的语法
[returned vdc instance] = $("[selector]").jqsDialog("showCommonPopup", [argObject]);
[returned vdc instance] = $("[selector]").jqsDialog
("showCommonPopup", messageText, [title], [icon], [popup], [parentsToClose]);
argObject
包含与 popupDialog
函数的 argObject
相同的所有属性,外加一个额外的属性 popup
,该属性用于接收现有的 VDC 实例以进行更新处理。以下代码片段演示了使用现有 VDC 实例(前三个对话框窗口)和新 VDC 实例(最后一个对话框窗口)级联显示确认和消息对话框:
//Show a confirmation dialog
var popup = container.jqsDialog("showCommonPopup", {
message: "This will overwrite your changes. Continue?",
title: "Warning",
icon: "warning",
actionButtonLabel: "Yes",
closeButtonLabel: "No"
});
popup.actionButton.on("click", function () {
//Dynamically switch to message dialog and set stopClose to true for keeping dialog open
container.jqsDialog("showCommonPopup", {
message: "Your request has been processed successfully",
title: "Information",
icon: "info",
stopClose: true,
popup: popup
});
popup.closeButton.on("click", function () {
//Dynamically switch to another dialog
container.jqsDialog("showCommonPopup", {
message: "There is an error closing the dialog.",
title: "Error",
icon: "error",
actionButtonLabel: "Ignore",
closeButtonLabel: "Cancel",
popup: popup
});
popup.actionButton.on("click", function () {
//Show a new dialog using everything else by default and
//close this and parent dialogs together
container.jqsDialog("showCommonPopup", {
message: "You will ignore the error and close all dialogs",
title: "Message",
icon: "info",
parentsToClose: popup
});
});
});
});
运行代码时,首先显示确认对话框:
点击 Yes 按钮,现有对话框将更新为消息对话框:
点击 OK 按钮,现有对话框将进一步更新为错误确认对话框:
点击 Ignore 按钮,将在前一个对话框上创建一个新的消息对话框。点击 OK 按钮将关闭活动对话框及其父对话框。
加载部分视图和数据
showDataPopup
函数旨在将 AJAX 调用获取的数据加载到对话框窗口中的 MVC 部分视图。以下是调用该函数的语法:
[returned vdc instance] = $("[selector]").jqsDialog("showDataPopup", [argObject]);
对话框的 argObject
属性与前面列出的 popupDialog
函数的属性相同。 AJAX 调用属性及其他一些附加项已添加到参数对象中。以下是调用代码示例:
var popup = container.jqsDialog("showDataPopup", {
//For Ajax call
url: ["(URL string)"],
data: [undefined],
type: ["GET"],
contentType: ["application/x-www-form-urlencoded; charset=utf-8"],
cache: [true - Ajax() default],
async: [true - Ajax() default],
beforeSend: [undefined]
success: [undefined],
error: [undefined],
complete: [undefined],
//Additional
args.stopProgress: [undefined or false], //Set true to disable built-in progress bar.
popup: [undefined] //existing vdc instance
//Remaining properties are the same as those for popupDialog function
. . .
});
AJAX 调用的输入参数属性和默认值与本地 JQuery Ajax()
函数相同,但仅包含参数对象中常用的属性。dataType
属性未暴露给消费者。因为它始终以 JSON 类型返回数据,所以它被硬编码为 "json"
。尽管回调的 success
、error
和 complete
属性已发布,但在大多数情况下,我们使用 callForJsonView
函数中引发的 jsonViewSuccess
、jsonViewError
和 jsonViewComplete
事件。jsonViewSuccess
事件是我们用于从 AJAX 调用中获取任何部分视图和数据的事件。
在 callForJsonView
函数中:
container.trigger("JsonViewSuccess", { html: result.html, data: result.data });
在消费者代码中:
container.on("JsonViewSuccess", function (html, data) {
//Do something on html and data...
}
在 ASP.NET MVC 应用程序中,控制器通常会将部分视图与数据模型返回到调用 @Html.Partial
或 @Html.RenderPartial
辅助函数的父视图所在的位置。这种方法无法实际用于将任何部分视图和数据加载到动态生成或更新的对话框中。我们需要 JQuery 插件中的逻辑将部分视图 HTML 文本注入对话框内容,并还将 AJAX 调用返回的数据绑定到 HTML 元素。因此,MVC 控制器应将这些项返回给 AJAX 调用者:
- 一个空的模型,用于部分视图中的字段类型映射。
- 一个包含以下内容的数组:
- 生成的局部视图 HTML 文本
- 包含所有请求数据记录的 JSON 对象实例
在页面控制器的方法中:
//The product parameter contains data obtained from data source
return PartialViewJson("../Demo/_Product", new Product(), product);
在 BaseController.cs 中:
protected JsonViewResult PartialViewJson
(string viewName, object model = null, params object[] data)
{
//viewName is used for getting the partial view for streaming process
return new JsonViewResult
{
Data = new
{
html = RenderPartialViewToString(viewName, model),
data = Json(data, "application/json; charset=utf-8",
System.Text.Encoding.UTF8, JsonRequestBehavior.AllowGet).Data
}
};
}
protected string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewData.Model = model;
using (StringWriter sw = new StringWriter(CultureInfo.CurrentCulture))
{
//Streaming the partial view to HTML text
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView
(ControllerContext, viewName);
ViewContext viewContext = new ViewContext
(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
JsonViewResult
是一个派生自 System.Web.Mvc.JsonResult
类的自定义类型。如果您有兴趣,可以在演示项目中的 Classes\JsonViewResult.cs 文件中查看详细信息。
最后,dataDialog.js 页面插件中的消费者代码调用对话框插件中的 showDataPopup
函数。然后按以下顺序执行这些任务:
- 从服务器获取部分视图 HTML 和 JSON 对象数据
- 将 HTML 注入对话框内容,并使用数据字段渲染对话框结构
- 使用 KnockoutJS 处理来自
jsonViewSuccess
事件的 JSON 数据 - 使用 KnockoutJS 将数据绑定到 HTML 元素并显示完整的对话框
loadProductPopup: function (container, rowid) {
var popup = container.jqsDialog("showDataPopup", {
url: "/Products/LoadProductById/" + rowid,
width: 450,
title: "Product",
titleTag: "h3",
actionButtonLabel: "Save",
closeButtonLabel: "Cancel",
animation: "top,200"
});
container.on("jsonViewSuccess", function (html, data) {
//html already injected to dialog inside showDataPopup
var viewModel = ko.mapping.fromJS(data.data[0]);
var bindNode = document.getElementById("divPupContent");
if (bindNode) {
ko.cleanNode(bindNode);
ko.applyBindings(viewModel, bindNode);
}
container.off();
});
popup.actionButton.on("click", function () {
//Pass dialog as parent for closing together later
methods.saveProduct(container, popup);
});
}
您可以看到来自消费者端的代码多么简单且行数少。使用其他内容的默认值,在 AJAX 调用部分的参数对象属性中仅指定了 url
参数值。如果传递复杂数据给 AJAX 调用,您只需在参数对象中添加另外两个属性:data: [object]
和 contentType: "application/json"
。该函数将自动使用 POST
方法处理请求。局部视图文件 _Product.cshtml 中的代码也非常简洁,且保持最小化(此情况下约十行。详情请参阅该文件)。生成的对话框窗口如下所示:
点击 Save 按钮时,会显示一个新的确认对话框。原始页面和数据对话框窗口现在都变成半透明背景。
点击 Yes 按钮时,将向服务器发送提交请求。收到状态响应后,现有 VDC 实例将更新为消息对话框以进行状态通知。
在确认对话框和状态通知对话框之间,如果存在任何延迟,可以使用同一个 VDC 实例加载另一个动态窗口来显示进度状态(请参阅下一节)。
上述示例仅展示了对话框窗口上数据的简单形式。对话框上的数据和显示结构可以更复杂,例如包含多个 JqGrid 或 KnockoutJS 网格的分层数据集。您可以使用 jqsDialog 插件的数据加载功能来实现这些结构。
进度条窗口
进度条窗口使用基础对话框结构,但加载动画图像到对话框内容,可带或不带标题和页脚。以下是调用进度条显示的语法:
[returned vdc instance] = $("[selector]").jqsDialog
("showProgressBar", [existingDialog], [keepParts])
得益于动态更新功能,可以使用同一个 VDC 实例在进度条和其他任何类型的对话框之间切换。如果 existingDialog
参数有值,进度条将处于更新的 VDC 模式。否则,将创建一个独立的 VDC 新实例来显示进度条。进度条窗口的外观还基于 keepParts
参数的 boolean
值。
-
当为
keepParts
参数传递true
时,标题和带按钮的页脚将保留,只有内容被动画图像替换。示例是演示项目中 AJAX 调用成功加载数据之前出现的进度条。在这种情况下,将渲染对话框的完整结构,但进度条图像会注入到内容中,直到被局部视图和数据结果替换。等待期间,页脚上的 Save 按钮将被禁用。这种类型的进度条显示是数据加载对话框的默认行为,可以通过在调用showDataPopup
函数时将参数对象属性设置为"stopProgress: true"
来禁用。 -
当
keepParts
参数未定义或设置为false
时,进度条将显示在弹出窗口中,标题为“Please wait...”且页脚无按钮。这种情况可以在演示项目中看到,在确认对话框点击 Save 按钮提交数据后有一个模拟的处理延迟。通常,在收到服务器的任何反馈后,此进度条窗口应切换为消息对话框,以进行成功或错误状态通知。
请注意,我在显示进度条时仍然保留了对话框右角的 x 按钮。用户可以点击它来取消进程,如果进程时间过长或挂起。如果您不希望在进度条窗口上显示 x 按钮,可以通过调用对话框实例函数 setHeader
来移除它。
//Remove x button for progress bar (pass anything with not-null value)
popup.setHeader("no-x");
但别忘了在切换到的下一个对话框上将 x 按钮加回来。
//Reset header with x button
popup.setHeader();
对于进度条,在出现时通过任何点击外部操作来关闭它不是一个好习惯。因此,通过在 showProgressBar
函数中使用以下代码行,应始终禁用点击外部选项:
//Set type status used for re-setting click-outside for next updated dialog
popup.typeStatus = "progress";
//Always disable click-outside but doesn't change isClickOutside value
//when calling VDC's clickOutside function. Thus isClickOutside can be used to
//resume setting after progress bar dialog is switched to other type.
popup.clickOutside(false);
通过在 showCommonPopup
和 showDataPopup
函数中使用代码,点击外部选项应在随后的更新对话框中自动恢复(如果该选项最初对 VDC 实例启用)。
//Reset click-outside if updated from progress bar
if (args.popup.typeStatus == "progress" && args.clickOutside == undefined) {
args.popup.clickOutside(args.popup.isClickOutside);
}
在对话框页脚设置更多按钮
根据设计,对话框插件有两个预定义按钮。如果需要,可以通过在对话框创建或更新后调用 VDC 实例函数 setOtherBtn
来将其他按钮添加到对话框页脚。
[returned button instance] = containerInstance.setOtherBtn(label, [type])
以下是为对话框添加第三个按钮的示例。
//Call base popupDialog for demo purpose. Could call showCommonPopup.
var popup = container.jqsDialog("popupDialog", {
message: "<br/><br/>(Content could be anything injected)<br/>",
title: "Selection",
actionButtonLabel: "Upload",
closeButtonLabel: "Cancel",
width: 320
});
//Add a new button with function for click event.
var emailButton = popup.setOtherBtn("Email");
emailButton.on("click", function () {
container.jqsDialog("showCommonPopup", "You selected sending out an email",
"Message", "info", popup);
});
对话框窗口的屏幕截图如下:
非模态对话框
当作为非模态类型使用时,可能需要对 jqsDialog
插件进行一些考虑。
- 输入参数的
modal
属性默认设置为true
。将其设置为false
以渲染非模态类型的对话框。 - 输入参数的
fadeIn
属性默认设置为true
。将modal
设置为false
将自动将fadeIn
设置为false
。 - 非模态对话框只能在其基础父窗口上打开。不支持从非模态父对话框打开任何非模态子对话框。要从非模态父对话框打开此类子对话框,可以将输入参数的
fadeIn
属性设置为false
,这样就不会显示灰色背景。 - 当从具有启用点击外部选项的非模态父对话框打开子对话框时,不应为其设置点击外部选项。
- 如果启用了点击外部选项的非模态父对话框,则在从父对话框打开子对话框且不希望父对话框一起关闭时,需要禁用该选项。然后,您需要为子对话框输入参数对象设置
parentResetClickOutside
属性值,以便在关闭子对话框后恢复父对话框的点击外部选项。 - 非模态对话框的
id
属性值需要显式定义,以避免在从父窗口发送相同命令时打开多个相同的对话框。
以下是创建非模态对话框及其子对话框的代码片段,其中父对话框的点击外部选项已禁用并重新启用。
//Needs explicit Id for non-modal dialog to prevent opening the same dialog again.
var popup = container.jqsDialog("showCommonPopup", {
message: "It's the parent non-modal dialog window.",
title: "Non-modal Dialog",
actionButtonLabel: "Open Child",
closeButtonLabel: "Cancel",
clickOutside: true,
modal: false,
id: "non-modal-1"
});
//Needs to check button existence for IE 8
if (popup.actionButton != undefined) {
popup.actionButton.on("click", function (event) {
//Need to disable non-modal parent click-outside when opening child dialog from it
popup.clickOutside(false);
var popupChild = container.jqsDialog("showCommonPopup", {
message: "It's the child dialog opened from the non-modal parent.",
title: "Child Dialog",
fadeIn: false,
//Re-enable parent's click-outside option
parentResetClickOutside: popup
});
});
}
下面的屏幕截图显示了非模态对话框示例。请随时使用示例应用程序或您自己的应用程序来测试各种其他场景。
主题和样式文件
演示项目中的 jqsDialog
插件使用以下源文件的 CSS:
- bootstrap_spacelab.css:设置整体主题。
modal-header
、modal-body
、modal-footer
和btn
类默认用于对话框的标题、正文、页脚和按钮。切换到其他 Bootstrap 主题也可能改变对话框的外观和感觉。 - jqsDialog.css:为对话框结构提供特定的自定义样式。更改整体 Bootstrap 主题时,可能需要调整自定义样式。
- site.css:包含应用于网站所有页面的通用样式。例如,
btn
类已添加到此文件中,以覆盖 Bootstrap CSS 文件中按钮的一些设置。更新后的样式适用于所有页面上的按钮,包括对话框。
摘要
此处展示的 JQuery 对话框已实现文章开头列出的所有要求和功能。所有插件中的 JavaScript 函数都可以被任何平台的应用程序使用,尽管演示项目构建为 ASP.NET MVC 网站应用程序。通过动态更新功能,您可以将任何形式的数据或媒体注入对话框内容,或更改任何对话框组件,如标题、图标、页脚和按钮。您还可以根据需要自由修改插件和相关支持文件中的任何内容,以实现功能、主题和样式,从而使这些对话框更加丰富和强大。祝您编码愉快!
历史
- 2014 年 4 月 8 日:初始版本