Skip to content

Instantly share code, notes, and snippets.

@fxck
Created November 7, 2013 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fxck/7355187 to your computer and use it in GitHub Desktop.
Save fxck/7355187 to your computer and use it in GitHub Desktop.
ui bootstrap modal with windowClass applying to backdrop and setWindowClass() method
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