Merge "Fix the docker push"
[clamp.git] / src / main / resources / META-INF / resources / designer / lib / modal.js
1 angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2
3 /**
4  * A helper, internal data structure that acts as a map but also allows getting / removing
5  * elements in the LIFO order
6  */
7   .factory('$$stackedMap', function () {
8     return {
9       createNew: function () {
10         var stack = [];
11
12         return {
13           add: function (key, value) {
14             stack.push({
15               key: key,
16               value: value
17             });
18           },
19           get: function (key) {
20             for (var i = 0; i < stack.length; i++) {
21               if (key == stack[i].key) {
22                 return stack[i];
23               }
24             }
25           },
26           keys: function() {
27             var keys = [];
28             for (var i = 0; i < stack.length; i++) {
29               keys.push(stack[i].key);
30             }
31             return keys;
32           },
33           top: function () {
34             return stack[stack.length - 1];
35           },
36           remove: function (key) {
37             var idx = -1;
38             for (var i = 0; i < stack.length; i++) {
39               if (key == stack[i].key) {
40                 idx = i;
41                 break;
42               }
43             }
44             return stack.splice(idx, 1)[0];
45           },
46           removeTop: function () {
47             return stack.splice(stack.length - 1, 1)[0];
48           },
49           length: function () {
50             return stack.length;
51           }
52         };
53       }
54     };
55   })
56
57 /**
58  * A helper directive for the $modal service. It creates a backdrop element.
59  */
60   .directive('modalBackdrop', ['$timeout', function ($timeout) {
61     return {
62       restrict: 'EA',
63       replace: true,
64       templateUrl: 'template/modal/backdrop.html',
65       link: function (scope, element, attrs) {
66         scope.backdropClass = attrs.backdropClass || '';
67
68         scope.animate = false;
69
70         //trigger CSS transitions
71         $timeout(function () {
72           scope.animate = true;
73         });
74       }
75     };
76   }])
77
78   .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
79     return {
80       restrict: 'EA',
81       scope: {
82         index: '@',
83         animate: '='
84       },
85       replace: true,
86       transclude: true,
87       templateUrl: function(tElement, tAttrs) {
88         return tAttrs.templateUrl || 'template/modal/window.html';
89       },
90       link: function (scope, element, attrs) {
91         element.addClass(attrs.windowClass || '');
92         scope.size = attrs.size;
93
94         // moved from template to fix issue #2280
95         element.on('click', function(evt) {
96           scope.close(evt);
97         });
98
99         $timeout(function () {
100           // trigger CSS transitions
101           scope.animate = true;
102
103           /**
104            * Auto-focusing of a freshly-opened modal element causes any child elements
105            * with the autofocus attribute to lose focus. This is an issue on touch
106            * based devices which will show and then hide the onscreen keyboard.
107            * Attempts to refocus the autofocus element via JavaScript will not reopen
108            * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
109            * the modal element if the modal does not contain an autofocus element.
110            */
111           if (!element[0].querySelectorAll('[autofocus]').length) {
112             element[0].focus();
113           }
114         });
115
116         scope.close = function (evt) {
117           var modal = $modalStack.getTop();
118           if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
119             evt.preventDefault();
120             evt.stopPropagation();
121             $modalStack.dismiss(modal.key, 'backdrop click');
122           }
123         };
124       }
125     };
126   }])
127
128   .directive('modalTransclude', function () {
129     return {
130       link: function($scope, $element, $attrs, controller, $transclude) {
131         $transclude($scope.$parent, function(clone) {
132           $element.empty();
133           $element.append(clone);
134         });
135       }
136     };
137   })
138
139   .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
140     function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
141
142       var OPENED_MODAL_CLASS = 'modal-open';
143
144       var backdropDomEl, backdropScope;
145       var openedWindows = $$stackedMap.createNew();
146       var $modalStack = {};
147
148       function backdropIndex() {
149         var topBackdropIndex = -1;
150         var opened = openedWindows.keys();
151         for (var i = 0; i < opened.length; i++) {
152           if (openedWindows.get(opened[i]).value.backdrop) {
153             topBackdropIndex = i;
154           }
155         }
156         return topBackdropIndex;
157       }
158
159       $rootScope.$watch(backdropIndex, function(newBackdropIndex){
160         if (backdropScope) {
161           backdropScope.index = newBackdropIndex;
162         }
163       });
164
165       function removeModalWindow(modalInstance) {
166
167         var body = $document.find('body').eq(0);
168         var modalWindow = openedWindows.get(modalInstance).value;
169
170         //clean up the stack
171         openedWindows.remove(modalInstance);
172
173         //remove window DOM element
174         removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() {
175           modalWindow.modalScope.$destroy();
176           body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
177           checkRemoveBackdrop();
178         });
179       }
180
181       function checkRemoveBackdrop() {
182           //remove backdrop if no longer needed
183           if (backdropDomEl && backdropIndex() == -1) {
184             var backdropScopeRef = backdropScope;
185             removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
186               backdropScopeRef.$destroy();
187               backdropScopeRef = null;
188             });
189             backdropDomEl = undefined;
190             backdropScope = undefined;
191           }
192       }
193
194       function removeAfterAnimate(domEl, scope, emulateTime, done) {
195         // Closing animation
196         scope.animate = false;
197
198         var transitionEndEventName = $transition.transitionEndEventName;
199         if (transitionEndEventName) {
200           // transition out
201           var timeout = $timeout(afterAnimating, emulateTime);
202
203           domEl.bind(transitionEndEventName, function () {
204             $timeout.cancel(timeout);
205             afterAnimating();
206             scope.$apply();
207           });
208         } else {
209           // Ensure this call is async
210           $timeout(afterAnimating);
211         }
212
213         function afterAnimating() {
214           if (afterAnimating.done) {
215             return;
216           }
217           afterAnimating.done = true;
218
219           domEl.remove();
220           if (done) {
221             done();
222           }
223         }
224       }
225
226       $document.bind('keydown', function (evt) {
227         var modal;
228
229         if (evt.which === 27) {
230           modal = openedWindows.top();
231           if (modal && modal.value.keyboard) {
232             evt.preventDefault();
233             $rootScope.$apply(function () {
234               $modalStack.dismiss(modal.key, 'escape key press');
235             });
236           }
237         }
238       });
239
240       $modalStack.open = function (modalInstance, modal) {
241
242         openedWindows.add(modalInstance, {
243           deferred: modal.deferred,
244           modalScope: modal.scope,
245           backdrop: modal.backdrop,
246           keyboard: modal.keyboard
247         });
248
249         var body = $document.find('body').eq(0),
250             currBackdropIndex = backdropIndex();
251
252         if (currBackdropIndex >= 0 && !backdropDomEl) {
253           backdropScope = $rootScope.$new(true);
254           backdropScope.index = currBackdropIndex;
255           var angularBackgroundDomEl = angular.element('<div modal-backdrop></div>');
256           angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
257           backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
258           body.append(backdropDomEl);
259         }
260
261         var angularDomEl = angular.element('<div modal-window></div>');
262         angularDomEl.attr({
263           'template-url': modal.windowTemplateUrl,
264           'window-class': modal.windowClass,
265           'size': modal.size,
266           'index': openedWindows.length() - 1,
267           'animate': 'animate'
268         }).html(modal.content);
269
270         var modalDomEl = $compile(angularDomEl)(modal.scope);
271         openedWindows.top().value.modalDomEl = modalDomEl;
272         body.append(modalDomEl);
273         body.addClass(OPENED_MODAL_CLASS);
274       };
275
276       $modalStack.close = function (modalInstance, result) {
277         var modalWindow = openedWindows.get(modalInstance);
278         if (modalWindow) {
279           modalWindow.value.deferred.resolve(result);
280           removeModalWindow(modalInstance);
281         }
282       };
283
284       $modalStack.dismiss = function (modalInstance, reason) {
285         var modalWindow = openedWindows.get(modalInstance);
286         if (modalWindow) {
287           modalWindow.value.deferred.reject(reason);
288           removeModalWindow(modalInstance);
289         }
290       };
291
292       $modalStack.dismissAll = function (reason) {
293         var topModal = this.getTop();
294         while (topModal) {
295           this.dismiss(topModal.key, reason);
296           topModal = this.getTop();
297         }
298       };
299
300       $modalStack.getTop = function () {
301         return openedWindows.top();
302       };
303
304       return $modalStack;
305     }])
306
307   .provider('$modal', function () {
308
309     var $modalProvider = {
310       options: {
311         backdrop: true, //can be also false or 'static'
312         keyboard: true
313       },
314       $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
315         function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
316
317           var $modal = {};
318
319           function getTemplatePromise(options) {
320             return options.template ? $q.when(options.template) :
321               $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl,
322                 {cache: $templateCache}).then(function (result) {
323                   return result.data;
324               });
325           }
326
327           function getResolvePromises(resolves) {
328             var promisesArr = [];
329             angular.forEach(resolves, function (value) {
330               if (angular.isFunction(value) || angular.isArray(value)) {
331                 promisesArr.push($q.when($injector.invoke(value)));
332               }
333             });
334             return promisesArr;
335           }
336
337           $modal.open = function (modalOptions) {
338
339             var modalResultDeferred = $q.defer();
340             var modalOpenedDeferred = $q.defer();
341
342             //prepare an instance of a modal to be injected into controllers and returned to a caller
343             var modalInstance = {
344               result: modalResultDeferred.promise,
345               opened: modalOpenedDeferred.promise,
346               close: function (result) {
347                 $modalStack.close(modalInstance, result);
348               },
349               dismiss: function (reason) {
350                 $modalStack.dismiss(modalInstance, reason);
351               }
352             };
353
354             //merge and clean up options
355             modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
356             modalOptions.resolve = modalOptions.resolve || {};
357
358             //verify options
359             if (!modalOptions.template && !modalOptions.templateUrl) {
360               throw new Error('One of template or templateUrl options is required.');
361             }
362
363             var templateAndResolvePromise =
364               $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
365
366
367             templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
368
369               var modalScope = (modalOptions.scope || $rootScope).$new();
370               modalScope.$close = modalInstance.close;
371               modalScope.$dismiss = modalInstance.dismiss;
372
373               var ctrlInstance, ctrlLocals = {};
374               var resolveIter = 1;
375
376               //controllers
377               if (modalOptions.controller) {
378                 ctrlLocals.$scope = modalScope;
379                 ctrlLocals.$modalInstance = modalInstance;
380                 angular.forEach(modalOptions.resolve, function (value, key) {
381                   ctrlLocals[key] = tplAndVars[resolveIter++];
382                 });
383
384                 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
385                 if (modalOptions.controllerAs) {
386                   modalScope[modalOptions.controllerAs] = ctrlInstance;
387                 }
388               }
389
390               $modalStack.open(modalInstance, {
391                 scope: modalScope,
392                 deferred: modalResultDeferred,
393                 content: tplAndVars[0],
394                 backdrop: modalOptions.backdrop,
395                 keyboard: modalOptions.keyboard,
396                 backdropClass: modalOptions.backdropClass,
397                 windowClass: modalOptions.windowClass,
398                 windowTemplateUrl: modalOptions.windowTemplateUrl,
399                 size: modalOptions.size
400               });
401
402             }, function resolveError(reason) {
403               modalResultDeferred.reject(reason);
404             });
405
406             templateAndResolvePromise.then(function () {
407               modalOpenedDeferred.resolve(true);
408             }, function () {
409               modalOpenedDeferred.reject(false);
410             });
411
412             return modalInstance;
413           };
414
415           return $modal;
416         }]
417     };
418
419     return $modalProvider;
420   });