This repository has been archived by the owner on Aug 29, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
dialog.js
234 lines (216 loc) · 7.96 KB
/
dialog.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/**
* @ngdoc module
* @name material.components.dialog
*/
angular.module('material.components.dialog', [
'material.animations',
'material.services.compiler',
'material.services.aria'
])
.directive('materialDialog', [
'$$rAF',
MaterialDialogDirective
])
.factory('$materialDialog', [
'$timeout',
'$materialCompiler',
'$rootElement',
'$rootScope',
'$materialEffects',
'$animate',
'$aria',
MaterialDialogService
]);
function MaterialDialogDirective($$rAF) {
return {
restrict: 'E',
link: function(scope, element, attr) {
$$rAF(function() {
var content = element[0].querySelector('.dialog-content');
if (content && content.scrollHeight > content.clientHeight) {
element.addClass('dialog-content-overflow');
}
});
}
};
}
/**
* @ngdoc service
* @name $materialDialog
* @module material.components.dialog
*
* @description
*
* The $materialDialog service opens a dialog over top of the app.
*
* The `$materialDialog` service can be used as a function, which when called will open a
* dialog. Note: the dialog is always given an isolate scope.
*
* It takes one argument, `options`, which is defined below.
*
* Note: the dialog's template must have an outer `<material-dialog>` element.
* Inside, use an element with class `dialog-content` for the dialog's content, and use
* an element with class `dialog-actions` for the dialog's actions.
*
* When opened, the `dialog-actions` area will attempt to focus the first button found with
* class `dialog-close`. If no button with `dialog-close` class is found, it will focus the
* last button in the `dialog-actions` area.
*
* @usage
* <hljs lang="html">
* <div ng-controller="MyController">
* <material-button ng-click="openDialog($event)">
* Open a Dialog from this button!
* </material-button>
* </div>
* </hljs>
* <hljs lang="js">
* var app = angular.module('app', ['ngMaterial']);
* app.controller('MyController', function($scope, $materialDialog) {
* $scope.openDialog = function($event) {
* var hideDialog = $materialDialog({
* template: '<material-dialog>Hello!</material-dialog>',
* targetEvent: $event
* });
* };
* });
* </hljs>
*
* @returns {function} `hideDialog` - A function that hides the dialog.
*
* @paramType Options
* @param {string=} templateUrl The url of a template that will be used as the content
* of the dialog.
* @param {string=} template Same as templateUrl, except this is an actual template string.
* @param {DOMClickEvent=} targetEvent A click's event object. When passed in as an option,
* the location of the click will be used as the starting point for the opening animation
* of the the dialog.
* @param {boolean=} hasBackdrop Whether there should be an opaque backdrop behind the dialog.
* Default true.
* @param {boolean=} clickOutsideToClose Whether the user can click outside the dialog to
* close it. Default true.
* @param {boolean=} escapeToClose Whether the user can press escape to close the dialog.
* Default true.
* @param {string=} controller The controller to associate with the dialog. The controller
* will be injected with the local `$hideDialog`, which is a function used to hide the dialog.
* @param {object=} locals An object containing key/value pairs. The keys will be used as names
* of values to inject into the controller. For example, `locals: {three: 3}` would inject
* `three` into the controller, with the value 3.
* @param {object=} resolve Similar to locals, except it takes promises as values, and the
* toast will not open until all of the promises resolve.
* @param {string=} controllerAs An alias to assign the controller to on the scope.
* @param {element=} appendTo The element to append the dialog to. Defaults to appending
* to the root element of the application.
*/
function MaterialDialogService($timeout, $materialCompiler, $rootElement, $rootScope, $materialEffects, $animate, $aria) {
var recentDialog;
var dialogParent = $rootElement.find('body');
if ( !dialogParent.length ) {
dialogParent = $rootElement;
}
return showDialog;
function showDialog(options) {
options = angular.extend({
appendTo: dialogParent,
hasBackdrop: true, // should have an opaque backdrop
clickOutsideToClose: true, // should have a clickable backdrop to close
escapeToClose: true,
// targetEvent: used to find the location to start the dialog from
targetEvent: null,
transformTemplate: function(template) {
return '<div class="material-dialog-container">' + template + '</div>';
},
// Also supports all options from $materialCompiler.compile
}, options || {});
// Incase the user provides a raw dom element, always wrap it in jqLite
options.appendTo = angular.element(options.appendTo);
// Close the old dialog
recentDialog && recentDialog.then(function(destroyDialog) {
destroyDialog();
});
recentDialog = $materialCompiler.compile(options).then(function(compileData) {
// Controller will be passed a `$hideDialog` function
compileData.locals.$hideDialog = destroyDialog;
var scope = $rootScope.$new(true);
var element = compileData.link(scope);
var popInTarget = options.targetEvent && options.targetEvent.target &&
angular.element(options.targetEvent.target);
var closeButton = findCloseButton();
var backdrop;
configureAria(element.find('material-dialog'));
if (options.hasBackdrop) {
backdrop = angular.element('<material-backdrop class="opaque ng-enter">');
$animate.enter(backdrop, options.appendTo, null);
}
$materialEffects.popIn(element, options.appendTo, popInTarget, function() {
if (options.escapeToClose) {
$rootElement.on('keyup', onRootElementKeyup);
}
if (options.clickOutsideToClose) {
element.on('click', dialogClickOutside);
}
closeButton.focus();
});
return destroyDialog;
function findCloseButton() {
//If no element with class dialog-close, try to find the last
//button child in dialog-actions and assume it is a close button
var closeButton = element[0].querySelector('.dialog-close');
if (!closeButton) {
var actionButtons = element[0].querySelectorAll('.dialog-actions button');
closeButton = actionButtons[ actionButtons.length - 1 ];
}
return angular.element(closeButton);
}
function destroyDialog() {
if (destroyDialog.called) return;
destroyDialog.called = true;
if (backdrop) {
$animate.leave(backdrop);
}
if (options.escapeToClose) {
$rootElement.off('keyup', onRootElementKeyup);
}
if (options.clickOutsideToClose) {
element.off('click', dialogClickOutside);
}
$materialEffects.popOut(element, options.appendTo, function() {
element.remove();
scope.$destroy();
scope = null;
element = null;
if(popInTarget !== null) {
popInTarget.focus();
}
});
}
function onRootElementKeyup(e) {
if (e.keyCode == Constant.KEY_CODE.ESCAPE) {
$timeout(destroyDialog);
}
}
function dialogClickOutside(e) {
// If we click the flex container outside the backdrop
if (e.target === element[0]) {
$timeout(destroyDialog);
}
}
});
/**
* Inject ARIA-specific attributes appropriate for Dialogs
*/
function configureAria(element) {
var ROLE = Constant.ARIA.ROLE;
$aria.update(element, {
'role': ROLE.DIALOG
});
var dialogContent = element.find('.dialog-content');
if(dialogContent.length === 0){
dialogContent = element;
}
var defaultText = Util.stringFromTextBody(dialogContent.text(), 3);
$aria.expect(element, 'aria-label', defaultText);
}
return recentDialog;
}
}