|
|
|
angular.module("ui.bootstrap", ["ui.bootstrap.modal"]);
|
|
|
|
angular.module('ui.bootstrap.modal', [])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper, internal data structure that acts as a map but also allows getting / removing
|
|
|
|
* elements in the LIFO order
|
|
|
|
*/
|
|
|
|
.factory('$$stackedMap', function () {
|
|
|
|
return {
|
|
|
|
createNew: function () {
|
|
|
|
var stack = [];
|
|
|
|
|
|
|
|
return {
|
|
|
|
add: function (key, value) {
|
|
|
|
stack.push({
|
|
|
|
key: key,
|
|
|
|
value: value
|
|
|
|
});
|
|
|
|
},
|
|
|
|
get: function (key) {
|
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
|
if (key == stack[i].key) {
|
|
|
|
return stack[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
keys: function() {
|
|
|
|
var keys = [];
|
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
|
keys.push(stack[i].key);
|
|
|
|
}
|
|
|
|
return keys;
|
|
|
|
},
|
|
|
|
top: function () {
|
|
|
|
return stack[stack.length - 1];
|
|
|
|
},
|
|
|
|
remove: function (key) {
|
|
|
|
var idx = -1;
|
|
|
|
for (var i = 0; i < stack.length; i++) {
|
|
|
|
if (key == stack[i].key) {
|
|
|
|
idx = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stack.splice(idx, 1)[0];
|
|
|
|
},
|
|
|
|
removeTop: function () {
|
|
|
|
return stack.splice(stack.length - 1, 1)[0];
|
|
|
|
},
|
|
|
|
length: function () {
|
|
|
|
return stack.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper directive for the $modal service. It creates a backdrop element.
|
|
|
|
*/
|
|
|
|
.directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
replace: true,
|
|
|
|
templateUrl: 'template/modal/backdrop.html',
|
|
|
|
link: function (scope, element, attrs) {
|
|
|
|
|
|
|
|
//trigger CSS transitions
|
|
|
|
$timeout(function () {
|
|
|
|
scope.animate = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
scope.close = function (evt) {
|
|
|
|
var modal = $modalStack.getTop();
|
|
|
|
if (modal && modal.value.backdrop && modal.value.backdrop != 'static') {
|
|
|
|
evt.preventDefault();
|
|
|
|
evt.stopPropagation();
|
|
|
|
$modalStack.dismiss(modal.key, 'backdrop click');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}])
|
|
|
|
|
|
|
|
.directive('modalWindow', ['$timeout', function ($timeout) {
|
|
|
|
return {
|
|
|
|
restrict: 'EA',
|
|
|
|
scope: {
|
|
|
|
index: '@'
|
|
|
|
},
|
|
|
|
replace: true,
|
|
|
|
transclude: true,
|
|
|
|
templateUrl: 'template/modal/window.html',
|
|
|
|
link: function (scope, element, attrs) {
|
|
|
|
scope.windowClass = attrs.windowClass || '';
|
|
|
|
|
|
|
|
//trigger CSS transitions
|
|
|
|
$timeout(function () {
|
|
|
|
scope.animate = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
scope.close = function (evt) {
|
|
|
|
var modal = $modalStack.getTop();
|
|
|
|
if (modal) {
|
|
|
|
$modalStack.dismiss(modal.key, 'close click');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}])
|
|
|
|
|
|
|
|
.factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap',
|
|
|
|
function ($document, $compile, $rootScope, $$stackedMap) {
|
|
|
|
|
|
|
|
var backdropjqLiteEl, backdropDomEl;
|
|
|
|
var backdropScope = $rootScope.$new(true);
|
|
|
|
var body = $document.find('body').eq(0);
|
|
|
|
var openedWindows = $$stackedMap.createNew();
|
|
|
|
var $modalStack = {};
|
|
|
|
|
|
|
|
function backdropIndex() {
|
|
|
|
var topBackdropIndex = -1;
|
|
|
|
var opened = openedWindows.keys();
|
|
|
|
for (var i = 0; i < opened.length; i++) {
|
|
|
|
if (openedWindows.get(opened[i]).value.backdrop) {
|
|
|
|
topBackdropIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return topBackdropIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
$rootScope.$watch(backdropIndex, function(newBackdropIndex){
|
|
|
|
backdropScope.index = newBackdropIndex;
|
|
|
|
});
|
|
|
|
|
|
|
|
function removeModalWindow(modalInstance) {
|
|
|
|
|
|
|
|
var modalWindow = openedWindows.get(modalInstance).value;
|
|
|
|
|
|
|
|
//clean up the stack
|
|
|
|
openedWindows.remove(modalInstance);
|
|
|
|
|
|
|
|
//remove window DOM element
|
|
|
|
modalWindow.modalDomEl.remove();
|
|
|
|
|
|
|
|
//remove backdrop if no longer needed
|
|
|
|
if (backdropDomEl && backdropIndex() == -1) {
|
|
|
|
backdropDomEl.remove();
|
|
|
|
backdropDomEl = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
//destroy scope
|
|
|
|
modalWindow.modalScope.$destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
$document.bind('keydown', function (evt) {
|
|
|
|
var modal;
|
|
|
|
|
|
|
|
if (evt.which === 27) {
|
|
|
|
modal = openedWindows.top();
|
|
|
|
if (modal && modal.value.keyboard) {
|
|
|
|
$rootScope.$apply(function () {
|
|
|
|
$modalStack.dismiss(modal.key);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$modalStack.open = function (modalInstance, modal) {
|
|
|
|
openedWindows.add(modalInstance, {
|
|
|
|
deferred: modal.deferred,
|
|
|
|
modalScope: modal.scope,
|
|
|
|
backdrop: modal.backdrop,
|
|
|
|
keyboard: modal.keyboard
|
|
|
|
});
|
|
|
|
|
|
|
|
var angularDomEl = angular.element('<div modal-window></div>');
|
|
|
|
angularDomEl.attr('window-class', modal.windowClass);
|
|
|
|
angularDomEl.attr('index', openedWindows.length() - 1);
|
|
|
|
angularDomEl.html(modal.content);
|
|
|
|
|
|
|
|
var modalDomEl = $compile(angularDomEl)(modal.scope);
|
|
|
|
openedWindows.top().value.modalDomEl = modalDomEl;
|
|
|
|
body.append(modalDomEl);
|
|
|
|
|
|
|
|
if (backdropIndex() >= 0 && !backdropDomEl) {
|
|
|
|
backdropjqLiteEl = angular.element('<div modal-backdrop></div>');
|
|
|
|
backdropDomEl = $compile(backdropjqLiteEl)(backdropScope);
|
|
|
|
body.append(backdropDomEl);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.close = function (modalInstance, result) {
|
|
|
|
var modal = openedWindows.get(modalInstance);
|
|
|
|
if (modal) {
|
|
|
|
modal.value.deferred.resolve(result);
|
|
|
|
removeModalWindow(modalInstance);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.dismiss = function (modalInstance, reason) {
|
|
|
|
console.trace();
|
|
|
|
var modalWindow = openedWindows.get(modalInstance).value;
|
|
|
|
if (modalWindow) {
|
|
|
|
modalWindow.deferred.reject(reason);
|
|
|
|
removeModalWindow(modalInstance);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.getTop = function () {
|
|
|
|
return openedWindows.top();
|
|
|
|
};
|
|
|
|
|
|
|
|
$modalStack.dismissAll = function () {
|
|
|
|
var modal;
|
|
|
|
while (modal = openedWindows.top()) {
|
|
|
|
$rootScope.$apply(function () {
|
|
|
|
$modalStack.dismiss(modal.key);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $modalStack;
|
|
|
|
}])
|
|
|
|
|
|
|
|
.provider('$modal', function () {
|
|
|
|
|
|
|
|
var $modalProvider = {
|
|
|
|
options: {
|
|
|
|
backdrop: true, //can be also false or 'static'
|
|
|
|
keyboard: true
|
|
|
|
},
|
|
|
|
$get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
|
|
|
|
function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
|
|
|
|
|
|
|
|
var $modal = {};
|
|
|
|
|
|
|
|
function getTemplatePromise(options) {
|
|
|
|
return options.template ? $q.when(options.template) :
|
|
|
|
$http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
|
|
|
|
return result.data;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getResolvePromises(resolves) {
|
|
|
|
var promisesArr = [];
|
|
|
|
angular.forEach(resolves, function (value, key) {
|
|
|
|
if (angular.isFunction(value) || angular.isArray(value)) {
|
|
|
|
promisesArr.push($q.when($injector.invoke(value)));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return promisesArr;
|
|
|
|
}
|
|
|
|
|
|
|
|
$modal.open = function (modalOptions) {
|
|
|
|
|
|
|
|
var modalResultDeferred = $q.defer();
|
|
|
|
var modalOpenedDeferred = $q.defer();
|
|
|
|
|
|
|
|
//prepare an instance of a modal to be injected into controllers and returned to a caller
|
|
|
|
var modalInstance = {
|
|
|
|
result: modalResultDeferred.promise,
|
|
|
|
opened: modalOpenedDeferred.promise,
|
|
|
|
close: function (result) {
|
|
|
|
$modalStack.close(modalInstance, result);
|
|
|
|
},
|
|
|
|
dismiss: function (reason) {
|
|
|
|
$modalStack.dismiss(modalInstance, reason);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//merge and clean up options
|
|
|
|
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
|
|
|
|
modalOptions.resolve = modalOptions.resolve || {};
|
|
|
|
|
|
|
|
//verify options
|
|
|
|
if (!modalOptions.template && !modalOptions.templateUrl) {
|
|
|
|
throw new Error('One of template or templateUrl options is required.');
|
|
|
|
}
|
|
|
|
|
|
|
|
var templateAndResolvePromise =
|
|
|
|
$q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
|
|
|
|
|
|
|
|
|
|
|
|
templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
|
|
|
|
|
|
|
|
var modalScope = (modalOptions.scope || $rootScope).$new();
|
|
|
|
modalScope.$close = modalInstance.close;
|
|
|
|
modalScope.$dismiss = modalInstance.dismiss;
|
|
|
|
|
|
|
|
var ctrlInstance, ctrlLocals = {};
|
|
|
|
var resolveIter = 1;
|
|
|
|
|
|
|
|
//controllers
|
|
|
|
if (modalOptions.controller) {
|
|
|
|
ctrlLocals.$scope = modalScope;
|
|
|
|
ctrlLocals.$modalInstance = modalInstance;
|
|
|
|
angular.forEach(modalOptions.resolve, function (value, key) {
|
|
|
|
ctrlLocals[key] = tplAndVars[resolveIter++];
|
|
|
|
});
|
|
|
|
|
|
|
|
ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
|
|
|
|
}
|
|
|
|
|
|
|
|
$modalStack.open(modalInstance, {
|
|
|
|
scope: modalScope,
|
|
|
|
deferred: modalResultDeferred,
|
|
|
|
content: tplAndVars[0],
|
|
|
|
backdrop: modalOptions.backdrop,
|
|
|
|
keyboard: modalOptions.keyboard,
|
|
|
|
windowClass: modalOptions.windowClass
|
|
|
|
});
|
|
|
|
|
|
|
|
}, function resolveError(reason) {
|
|
|
|
modalResultDeferred.reject(reason);
|
|
|
|
});
|
|
|
|
|
|
|
|
templateAndResolvePromise.then(function () {
|
|
|
|
modalOpenedDeferred.resolve(true);
|
|
|
|
}, function () {
|
|
|
|
modalOpenedDeferred.reject(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
return modalInstance;
|
|
|
|
};
|
|
|
|
|
|
|
|
return $modal;
|
|
|
|
}]
|
|
|
|
};
|
|
|
|
|
|
|
|
return $modalProvider;
|
|
|
|
});
|