一个高级且易于使用的 AngularJS 模态对话框
更新了兼容 Angular 1.5x 和 TypeScript 的模态对话框
更新通知:已在此处添加更新后的 NgExDialog,以支持使用 Angular 1.5x 组件和 TypeScript 的 Web 应用程序。有关更多描述,请参阅添加到文章末尾的新部分。对于对具有相同功能和特性的 Angular 2 版本感兴趣的开发者,请参阅 Ng2ExDialog 的文章和示例代码。
引言
我之前创建了一个功能齐全的 JQuery 对话框插件,名为jqsDialog,用于构建网页。最近,在开发 AngularJS 网站应用程序时,我需要相同的模态对话框。尽管从开发者社区和其他来源可以获得许多现成的 AngularJS 模态对话框工具,但没有一个能像 jqsDialog
那样拥有高级功能。因此,我再次创建了我自己的 AngularJS 模态对话框库,命名为 NgExDialog
,以匹配 jqsDialog
提供的所有功能,除了非模态选项(这几乎没有实际意义)和进度条,因为大多数网站应用程序都使用 AJAX 加载器进行显示。
NgExDialog
具有以下特性
- 易于使用,调用代码标准化且简化
- 灵活且可定制,适用于通用的消息传递和数据显示目的
- 对话框可以打开到任意深度,可以单独关闭或保留打开,也可以与父级一起关闭,或者关闭所有对话框
- 可配置所有选项,例如拖动、动画、图标、灰色背景、单击外部关闭、取消确认等
- 可以为每个组件设置主题和样式,例如主对话框、标题栏、标题、图标、消息正文、消息文本、页脚和按钮
- 分发包含单个 JavaScript 文件和链接的 CSS 文件,如果启用了图标显示,还需要两个图像文件
- 该对话框仅依赖于angular.js 和bootstrap.css。不需要其他库或文件引用。
基于这些出色的特性,NgExDialog
的内部代码有些复杂。本文不打算讨论 ngExDialog.js 和 ngExDialog.css 文件中的编码细节,而是侧重于如何在 Web 应用程序中使用该工具,并解决与用例相关的一些主要问题。如果任何读者对 NgExDialog
的内部代码和结构感兴趣,请随时从下载的源代码中查看详细信息。对于不自明的代码块或行,总是有注释的。
对话框访问场景和语法
NgExDialog
被构建为一个 AngularJS 服务提供者。在示例中,打开对话框的代码都在 controller.js 中,没有顶层指令。在实际应用中,通过指令从 AngularJS 视图打开对话框不如从 AngularJS 控制器打开实用,因为对话框充当应用程序工作流过程中的条件性和动态弹出窗口。
打开对话框可以遵循以下场景(假设 NgExDialog
已注入到应用程序模块中,并且 exDialog
是调用的提供者名称)
-
如果只需要默认设置,或者只指定了正文文本、标题或图标,则使用简单的参数行来创建消息或确认对话框。
语法
[custom-object] = exDialog.openMessage($scope, "message-body", ["title"], ["icon"]); [premise-object] = exDialog.openConfirm($scope, "message-body", ["title"], ["icon"]);
-
如果需要除正文文本、标题或图标之外的任何非默认设置,则使用带有所需属性的参数对象来创建消息或确认对话框。
语法
[custom-object] = exDialog.openMessage($scope, parameter-object); [premise-object] = exDialog.openConfirm($scope, parameter-object);
-
始终传递带有所需属性的参数对象,以用于任何自定义或数据加载对话框。
语法
[custom-object] = exDialog.openPrime($scope, parameter-object);
参数对象接受以下列出的属性。同样的内容可以在 ngExDialog.js 文件中的 openMessage()
方法的上方找到。
@params {Object}:
//These for message and confirm types with built-in template only.
- title {String} - dialog title in header, default to "information" or configured
- icon {String} - values are 'info', 'warning', 'question', 'error',
default to "info" or configured
- message {String} - body message
- closeButtonLabel {String} - close button label, default to "OK" for alert
- and "No" for confirm, or configured
- actionButtonLabel {String} - Action button label, default to "Yes" for confirm,
- or configured
- closeAllDialogs {Boolean} - close all dialogs including parents
- keepOpenForAction {Boolean} - keep previous confirmation dialog open when clicking
- action button, default to undefined, or configured
- keepOpenForClose {Boolean} - keep previous confirmation dialog open when clicking
- close button, default to undefined, or configured
- dialogAddClass {String}
- headerAddClass {String}
- titleAddClass {String}
- bodyAddClass {String}
- messageAddClass {String}
- footerAddClass {String}
- actionButtonAddClass {String}
- closeButtonAddClass {String}
//These for all types including custom template.
- scope {Object} - source scope
- template {String} - id for ng-template script, url for file,
- or plain string containing HTML text. Required for openPrime()
- controller {String} - required if the custom template needs it.
- width (String} - dialog width, configured
- closeByXButton {Boolean} - show x close button, default true, or configured
- closeByEscKey {Boolean} - default true, or configured
- closeByClickOutside {Boolean} - default true, or configured
- beforeCloseCallback {String|Function} - user supplied function name/function
- called before closing dialog (if set)
- grayBackground {Boolean} - default true, or configured
- cacheTemplate {Boolean} - default true, or configured
- draggable {Boolean} - default true, or configured
- animation {Boolean} - default true, or configured
基本用例示例
作为示例演示,您可以下载源代码并将其文件夹和文件添加到任何支持 HTML 和 JavaScript 的网站项目中。ngExdialog
的所有功能都应与最新版本的 Internet Explorer、Google Chrome 和 Firefox 兼容。不保证其他浏览器类型和版本能做到完全相同。
运行 index.html 将启动页面,显示打开对话框的链接。您甚至可以通过调用 NgExDialog
服务来测试任何其他情况。
-
仅使用必需的正文文本打开一个信息消息对话框
exDialog.openMessage($scope, "This is called from a simple line of parameters.");
-
仅使用必需的消息文本打开一个警告消息对话框
exDialog.openMessage($scope, "This is called from a simple line of parameters.", "Warning", "warning");
-
仅使用必需的正文文本打开一个确认对话框
exDialog.openConfirm($scope, "Would you like to close the dialog and open another one?").then(function (value) { exDialog.openMessage($scope, "This is another dialog."); });
-
打开一个禁用动画和拖动的消息对话框(请注意,默认情况下所有对话框都启用动画和拖动功能)
exDialog.openMessage({ scope: $scope, message: "Animation and drag-move disabled.", animation: false, draggable: false });
显示的对话框具有所有默认的标题、图标和按钮,但没有动画和拖动效果。您可以通过单击演示页面上的“无动画和拖动对话框”链接来查看结果。
-
打开一个自定义数据表单对话框
exDialog.openPrime({ scope: $scope, template: 'Pages/_Product.html', controller: 'productController', width: '450px' });
在这种情况下,
NgExDialog
提供了对话框的主要框架功能。所有内容和页面-对话框通信过程都定义在指定的模板和控制器中,包括操作按钮和关闭按钮,以及所有内容样式。这将使开发人员在设计和实现数据表单及其操作方面更加灵活。
对话框显示模板
NgExDialog
中用于消息和确认对话框的内置模板通常能满足大多数常见需求。主题和样式甚至可以在单个组件级别进行自定义。如果您需要修改它或在其内部添加新组件,可以在包含的 commonDialog_0.html 文件中进行更改,然后通过在 app.js 文件中切换配置项来将其用作默认模板。
angular.module('smApp', ['smApp.controllers', 'smApp.AppServices', 'ngExDialog', function () {
}])
//Dialog default settings.
.config(['exDialogProvider', function (exDialogProvider) {
exDialogProvider.setDefaults({
//template: 'ngExDialog/commonDialog.html', //from cache
template: 'ngExDialog/commonDialog_0.html', //from file
. . .
});
}]);
如果添加的组件需要其控制器的代码支持,您也可以在 ngExDialog.js 文件中的 commonDialogController
中进行更改。
对于消息或确认对话框以外的任何类型,例如数据表单对话框(请参阅示例源代码中 _Product.html 模板和 controller.js 中的 productController
),应创建特定的自定义模板及其控制器。在这种情况下,对话框使用 NgExDialog
的核心功能与环境进行交互。自定义模板负责可见对话框区域的内容。因此,模板及其控制器之间的数据处理、通信以及对话框的外观将由您自己的代码处理。
您可以选择几种形式的模板
-
ng-template
脚本代码中的id
属性名称<script type="text/ng-template" id="customDialogTemplate"> <!--HTML code here--> . . . </script>
-
模板 HTML 文件的 URL 路径,例如“/Pages/_Product.html”。
-
纯文本字符串形式的 HTML,以开标签符号“
<
”开头。
模板加载器将解析输入值并自动选择正确的模板形式和内容。不需要其他指示器或标志。除非您通过将 cacheTemplate
输入参数对象的属性设置为 false
来更改此默认行为,否则将在使用前缓存模板(如果不存在相同的模板缓存)。
关闭或保留对话框
当用作多层通用消息和确认对话框时,NgExDialog
和 jqsDialog
之间存在一个主要的结构差异。jqsDialog
主要重用现有的对象实例,并动态更改对象的内容,例如正文文本、标题、图标和/或按钮,以用于子对话框。然而,NgExDialog
始终使用新的对象实例来打开子对话框。默认情况下,它首先关闭父对话框,然后打开其子对话框。与大多数其他 AngularJs 模态对话框工具不同,NgExDialog
提供了在显示子对话框时将任何级别的父对话框保留在后台的选项。启用此功能至少有以下好处
- 一些依赖进程需要父对话框和子对话框共存,即使是非数据访问对话框。
- 在需要时,用户可以看到为工作流加载的所有对话框。
- 可以避免因对话框转换而产生的闪烁和抖动视觉效果。
可以使用参数对象的输入属性来启用将保留打开的对话框的选项
exDialog.openConfirm({
scope: $scope,
. . .,
keepOpenForAction: true,
keepOpenForClose: true
});
对于确认类型的对话框,keepOpenForAction
用于在单击操作按钮(如是、确定、前往或继续)时保持对话框打开,而 keepOpenForClose
用于在单击关闭按钮(如否或取消)时保持对话框打开。对于只有一个确定、前往或继续按钮的消息类型对话框,只有 keepOpenForClose
可用。
在大多数情况下,在使用选项保留父对话框打开时,需要从子对话框中发出命令,例如也关闭父级或关闭所有对话框。
如果还要从子对话框的代码中关闭其父级
exDialog.openMessage({
scope: $scope,
. . .,
closeImmediateParent: true
});
如果关闭所有对话框
exDialog.openMessage({
scope: $scope,
. . .,
closeAllDialogs: true
});
现有的父对话框始终位于新打开的子对话框后面。如果父对话框的大小小于重叠的子对话框,则可能根本看不到父对话框。由于 NgExDialog
具有拖动功能(稍后描述),因此可以移动子对话框以查看下方的父对话框。
关闭对话框时运行任务
对于对话框,命令通常从操作按钮启动。应用程序工作流有时需要在关闭对话框时运行其他任务,例如取消警告、进一步确认或重定向到其他页面。关闭对话框时有三种选项可用于运行任务。
-
使用自定义回调函数处理通用消息或确认对话框的任何基本屏幕。您可以在输入参数对象的
beforeCloseCallback
属性中指定一个回调函数exDialog.openConfirm({ scope: $scope, actionButtonLabel: "Continue", closeButtonLabel: "Cancel", message: "What next step would you like to take?", beforeCloseCallback: function (value) { var rtnPremise = exDialog.openConfirm({ scope: $scope, message: "Do you really want to cancel it?" }); return rtnPremise; } });
响应取消确认时,单击是按钮将取消工作流并关闭所有弹出屏幕;单击否按钮将返回到之前的基本屏幕,一切保持原样。
-
为确认对话框的基本屏幕使用关闭前提对象。
exDialog.openConfirm($scope, "Would you like to open a second dialog?"). then(function (value) { exDialog.openMessage($scope, "This is the second dialog."); }, function (reason) { exDialog.openMessage($scope, "The dialog has been closed."); });
默认情况下,对话框关闭后会执行响应关闭对话框的任务。因此,此场景最适用于不返回到基本对话框屏幕的工作流。下图显示了关闭取消确认对话框并打开最终通知消息对话框的过渡时刻。
-
直接从任何基本对话框的关闭按钮事件,为具有自定义模板的对话框打开确认对话框。这种方法非常直接,因为关闭按钮及其属性是在自定义模板中指定的。这是数据表单对话框示例中取消警告和确认的代码。
exDialog.openConfirm({ scope: $scope, title: "Cancel Warning", icon: "warning", message: "Do you really want to cancel the data editing?" }).then(function (value) { exDialog.openMessage({ scope: $scope, title: "Notification", message: "The editing has been cancelled." }); }, function (reason) { exDialog.openMessage({ scope: $scope, title: "Notification", message: "The editing will continue." }); });
屏幕截图显示了单击“取消警告”对话框中的“否”按钮时的结果
可拖动对话框
可拖动的对话框允许用户查看下方页面内容的任何部分,因此是一个用户友好的附加功能。NgExDialog
完全可拖动,并且能够很好地适应屏幕大小调整。默认情况下启用拖动选项。您可以从应用程序配置级别关闭此功能,或像前面示例所示为任何单个对话框禁用它。
在使用拖动功能时,有几个特别的评论值得提及。
-
当为包含输入类型元素的自定义模板的任何对话框启用拖动时,您需要为每个输入元素指定额外的
ng-focus
和ng-blur
属性,如 _Product.html 示例中所示<input type="text" class="form-control" data-ng-model="model.Product.ProductName" ng-focus="setDrag(true)" ng-blur="setDrag(false)" />
这是因为
NgExDialog
中的拖动指令不会调用mousedown
事件的preventDefault()
函数,如果调用了该函数,将禁用对话框上的输入字段。但根据 HTML 默认设置,当尝试用鼠标突出显示输入字段中的文本时,整个对话框会移动,导致正常的文本突出显示功能失败。因此,在NgExDialog
内部设置了一个标志,该标志从这些输入字段接收布尔值,以便在鼠标指针位于任何输入字段上或离开任何输入字段时分别禁用和重新启用拖动操作。在下面的屏幕截图中,当输入字段获得焦点时,对话框无法被拖动和移动
-
当拖动和移动时,
NgExDialog
还会禁用对话框和下方页面上显示文本的选择。有时,显示文本的选择仍然会发生,特别是如果对话框被移动到窗口边缘或移出窗口边缘。这主要是由于浏览器兼容性问题,或者浏览器不支持在拖动指令中使用的这行 JavaScript 代码。window.getSelection().removeAllRanges();
-
调整窗口大小时,如果对话框自打开以来未被拖动,则对话框将始终在窗口内重新居中。如果对话框被拖动然后调整窗口大小,对话框的水平位置将正常重新调整。然而,对话框的垂直位置将固定在之前拖动结束的点上。这不是错误。这种行为是故意实现的,作为解决对话框垂直居中问题的变通方法。如果垂直调整窗口大小以减小窗口的高度,则对话框的全部或部分可能会超出窗口,处于对话框固定位置。在这种情况下,用户可以在调整窗口大小之前重新调整对话框的位置。
自定义内置模板的样式
可以使用内置模板为通用消息或确认对话框的组件指定其他 CSS 类。例如,当对话框在屏幕上显示而没有灰色背景时,它需要一个单行边框。然后,我们可以将 dialogAddClass
属性添加到输入参数对象中,并在对话框级别指定 border-to-dialog
CSS 类。
exDialog.openMessage({
scope: $scope,
title: "No Icon, No Gray",
icon: "none",
message: "This is called by passing a parameter object",
grayBackground: false,
dialogAddClass: 'border-to-dialog'
});
通过在参数对象中添加 headerAddClass
和 footerAddClass
属性,还可以轻松地更改特定对话框的标题栏和页脚样式,然后创建相应的 CSS 类。
exDialog.openMessage({
scope: $scope,
title: "Look Different",
icon: "none",
message: "Show header and footer in other styles.",
headerAddClass: 'my-dialog-header',
footerAddClass: 'my-dialog-footer'
});
可在输入参数对象属性列表中找到用于添加对话框组件 CSS 类的完整列表,该列表在开头部分有描述。如果需要使所有对话框在整个应用程序中具有相同的外观,也可以将任何或所有这些属性设置为应用程序级别的配置的默认值。
使用浏览器导航关闭对话框
在 AngularJS 页面上,任何浏览器重定向到其他站点都会自动关闭任何打开的对话框。但是,在浏览器的后退和前进按钮方面存在一些问题。
问题 #1:在没有历史活动的情况下打开对话框的页面时,浏览器后退按钮可用。这可能是由加载了位置 URL 的对话框模板 HTML 引起的。这种虚假的按钮启用行为应避免,尽管单击后退或前进按钮不起作用。解决方法是简单地将 AngularJS 的 $location
注入到使用对话框的控制器中,而无需添加其他代码。
//Inject $location to controller that uses dialog to remove unwanted behaviors
//for browser navigation buttons.
.controller('sampleController', ['$scope', '$timeout', 'exDialog',
'$location', function ($scope, $timeout, exDialog, $location) {
//. . .
}])
问题 #2:在具有任何历史活动和打开对话框的页面上进行后退和前进浏览。这可能会在切换到之前访问过的页面后,将当前模态对话框仍然显示在背景之上。以下方法用于解决此问题。
- 添加一个函数 hasOpenDialog(),用于返回当前作用域中任何打开对话框的布尔标志。
hasOpenDialog: function () { if (document.querySelector('.dialog-main')) { return true; } else { return false; } }
- 在 HTML 主体控制器(AngularJS SPA 应用程序中的顶层控制器)中,将代码放在
$locationChangeStart
事件处理程序中。当路由位置更改并且调用hasOpenDialog()
返回true
时,它将关闭任何打开的对话框。.controller('bodyController', ['$scope', 'exDialog', '$location', function ($scope, exDialog, $location) { //Close dialog if any when clicking browser navigation buttons. $scope.$on('$locationChangeStart', function (event, newUrl, oldUrl) { if (newUrl != oldUrl && exDialog.hasOpenDialog()) { exDialog.closeAll(); } }); }])
以下屏幕截图说明了浏览器后退操作。
导航到第二页并在该页面上打开对话框时
单击浏览器后退按钮
对话框自动关闭,进程返回到第一个主页
兼容 Angular 1.5x TypeScript 的更新
随着 Angular 2 的稳定,Angular 1.x 被认为是一个过时的 JavaScript 框架。然而,许多现有的使用 Angular 1.x 的 Web 应用程序仍然需要工具或库支持。我想让 NgExDialog
兼容 Angular 1.3x 至 1.5x 和 TypeScript,并且可以从 Angular 1.5x 组件代码调用,只需进行少量更改,而不是将 NgExDialog
升级到完整的 TypeScript 和组件结构。更改概述如下
- 将
NgExDialog
的 Angular JS 代码转换为 TypeScript 代码,并创建了 ngExDialog.ts 文件,该文件适用于 TypeScript 版本 1.8.3 至 2.1.5。 - 编辑了代码以支持 Angular 1.3x 至 1.5x。下载的源代码包含 1.5.8 版本。
- 检查了所有功能和特性均未受影响。
演示示例应用程序使用 Visual Studio 2015 或 2017 运行。第二个示例页面上的代码已重写,以便从 Angular 1.5x 组件控制器类打开对话框。
组件节点在 secondSample.html 中指定
<div class="container">
<sample-second></sample-second>
</div>
视图内容来自 _sampleSecondTemplate.html
<div >
<h4 class="panel-indent">Test Browser Navigation Buttons</h4>
<a class="hy-link cursor-pointer" ng-click="vm.openSimpleInfo()">
Open Information Dialog</a>
</div>
所有 TypeScript 类和接口都在 secondSampleComponent.ts 中。以下是 SecondSampleController
用于打开消息对话框的主要代码行(您可以在该文件中查看所有其他详细信息)。
class SecondSampleController implements ISecondSampleController {
//Inject dependencies.
static $inject = ['exDialog', '$scope']
constructor(private exDialog, private $scope) { }
openSimpleInfo() {
this.exDialog.openMessage(this.$scope, "Open a dialog on second page.");
}
}
摘要
此处介绍的 AngularJS 模态对话框 NgExDialog
功能丰富且易于使用。我很高兴与开发者社区分享这个工具和示例演示代码。希望 Web 开发者会喜欢这个工具和代码。欢迎任何反馈。
历史
- 2015年5月18日
- 原始帖子
- 2015年8月24日
- 添加了单击浏览器后退或前进按钮时关闭对话框的解决方案。有关详细信息,请参阅“使用浏览器导航关闭对话框”部分。
- 源代码文件也已更新。
- 2017年3月28日
- 更新了
NgExDialog
以兼容 Angular 1.5x 和 TypeScript。在第二个示例页面的组件结构中提供了一个使用示例。 - 与 Visual Studio 2015 或 2017 运行的示例代码已添加到下载列表中
- 更新了