Created
November 7, 2013 14:15
-
-
Save fxck/7355187 to your computer and use it in GitHub Desktop.
ui bootstrap modal with windowClass applying to backdrop and setWindowClass() method
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', ['$timeout', function ($timeout) { | |
return { | |
restrict: 'EA', | |
replace: true, | |
templateUrl: 'template/modal/backdrop.html', | |
link: function (scope, element, attrs) { | |
scope.windowClass = attrs.windowClass || ''; | |
//trigger CSS transitions | |
$timeout(function () { | |
scope.animate = true; | |
}); | |
} | |
}; | |
}]) | |
.directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { | |
return { | |
restrict: 'EA', | |
scope: { | |
index: '@' | |
}, | |
replace: true, | |
transclude: true, | |
templateUrl: 'template/modal/window.html', | |
link: function (scope, element, attrs) { | |
scope.windowClass = attrs.windowClass || ''; | |
scope.$on('setWindowClass', function(e, data) { | |
scope.windowClass = data; | |
}); | |
//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.target === evt.currentTarget)) { | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
$modalStack.dismiss(modal.key, 'backdrop click'); | |
} | |
}; | |
} | |
}; | |
}]) | |
.factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', '$timeout', | |
function ($document, $compile, $rootScope, $$stackedMap, $timeout) { | |
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(openedWindows.length, function(noOfModals){ | |
body.toggleClass('modal-open', openedWindows.length() > 0); | |
}); | |
$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.removeClass('in').on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function() { | |
$(this).remove(); | |
}); | |
//remove backdrop if no longer needed | |
//var backdropIndex = backdropIndex(); | |
if (backdropIndex() == -1) { | |
backdropDomEl.removeClass('in').on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function() { | |
$(this).remove(); | |
}); | |
backdropDomEl = undefined; | |
} | |
//destroy scope | |
modalWindow.modalScope.$destroy(); | |
if (backdropIndex() != -1) { | |
var modal = openedWindows.top(); | |
//$rootScopesetWindowClass | |
backdropScope.windowClass = modal.value.windowClass; | |
} | |
} | |
$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, | |
windowClass: modal.windowClass | |
}); | |
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); | |
backdropjqLiteEl = angular.element('<div modal-backdrop></div>'); | |
if (backdropIndex() >= 0 && !backdropDomEl) { | |
backdropjqLiteEl.attr('window-class', modal.windowClass); | |
backdropDomEl = $compile(backdropjqLiteEl)(backdropScope); | |
body.append(backdropDomEl); | |
} else { | |
backdropScope.windowClass = modal.windowClass; | |
} | |
}; | |
$modalStack.close = function (modalInstance, result) { | |
var modalWindow = openedWindows.get(modalInstance).value; | |
if (modalWindow) { | |
modalWindow.deferred.resolve(result); | |
removeModalWindow(modalInstance); | |
} | |
}; | |
$modalStack.dismiss = function (modalInstance, reason) { | |
var modalWindow = openedWindows.get(modalInstance).value; | |
if (modalWindow) { | |
modalWindow.deferred.reject(reason); | |
removeModalWindow(modalInstance); | |
} | |
}; | |
$modalStack.setWindowClass = function (modalInstance, windowClass) { | |
var modalWindow = openedWindows.get(modalInstance).value; | |
if (windowClass) { | |
modalWindow.modalScope.$broadcast('setWindowClass', windowClass); | |
backdropScope.windowClass = windowClass; | |
} | |
}; | |
$modalStack.getTop = function () { | |
return openedWindows.top(); | |
}; | |
return $modalStack; | |
}]) | |
.provider('$modal', function () { | |
var defaultOptions = { | |
backdrop: true, //can be also false or 'static' | |
keyboard: true | |
}; | |
return { | |
options: defaultOptions, | |
$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(this, result); | |
}, | |
dismiss: function (reason) { | |
$modalStack.dismiss(this, reason); | |
}, | |
setWindowClass: function (windowClass) { | |
$modalStack.setWindowClass(this, windowClass); | |
} | |
}; | |
//merge and clean up options | |
modalOptions = angular.extend({}, defaultOptions, 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(); | |
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; | |
}] | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment