// The `$dialogProvider` can be used to configure global defaults for your // `$dialog` service. var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']); dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){ $scope.title = model.title; $scope.message = model.message; $scope.buttons = model.buttons; $scope.close = function(res){ dialog.close(res); }; }]); dialogModule.provider("$dialog", function(){ // The default options for all dialogs. var defaults = { backdrop: true, dialogClass: 'modal', backdropClass: 'modal-backdrop', transitionClass: 'fade', triggerClass: 'in', dialogOpenClass: 'modal-open', resolve:{}, backdropFade: false, dialogFade:false, keyboard: true, // close with esc key backdropClick: true // only in conjunction with backdrop=true /* other options: template, templateUrl, controller */ }; var globalOptions = {}; var activeBackdrops = {value : 0}; // The `options({})` allows global configuration of all dialogs in the application. // // var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){ // // don't close dialog when backdrop is clicked by default // $dialogProvider.options({backdropClick: false}); // }); this.options = function(value){ globalOptions = value; }; // Returns the actual `$dialog` service that is injected in controllers this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector", function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) { var body = $document.find('body'); function createElement(clazz) { var el = angular.element("
"); el.addClass(clazz); return el; } // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object // containing at lest template or templateUrl and controller: // // var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'}); // // Dialogs can also be created using templateUrl and controller as distinct arguments: // // var d = new Dialog('path/to/dialog.html', MyDialogController); function Dialog(opts) { var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts); this._open = false; this.backdropEl = createElement(options.backdropClass); if(options.backdropFade){ this.backdropEl.addClass(options.transitionClass); this.backdropEl.removeClass(options.triggerClass); } this.modalEl = createElement(options.dialogClass); if(options.dialogFade){ this.modalEl.addClass(options.transitionClass); this.modalEl.removeClass(options.triggerClass); } this.handledEscapeKey = function(e) { if (e.which === 27) { self.close(); e.preventDefault(); self.$scope.$apply(); } }; this.handleBackDropClick = function(e) { self.close(); e.preventDefault(); self.$scope.$apply(); }; } // The `isOpen()` method returns wether the dialog is currently visible. Dialog.prototype.isOpen = function(){ return this._open; }; // The `open(templateUrl, controller)` method opens the dialog. // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired. Dialog.prototype.open = function(templateUrl, controller){ var self = this, options = this.options; if(templateUrl){ options.templateUrl = templateUrl; } if(controller){ options.controller = controller; } if(!(options.template || options.templateUrl)) { throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.'); } this._loadResolves().then(function(locals) { var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new(); self.modalEl.html(locals.$template); if (self.options.controller) { var ctrl = $controller(self.options.controller, locals); self.modalEl.contents().data('ngControllerController', ctrl); } $compile(self.modalEl)($scope); self._addElementsToDom(); body.addClass(self.options.dialogOpenClass); // trigger tranisitions setTimeout(function(){ if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); } if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); } }); self._bindEvents(); }); this.deferred = $q.defer(); return this.deferred.promise; }; // closes the dialog and resolves the promise returned by the `open` method with the specified result. Dialog.prototype.close = function(result){ var self = this; var fadingElements = this._getFadingElements(); body.removeClass(self.options.dialogOpenClass); if(fadingElements.length > 0){ for (var i = fadingElements.length - 1; i >= 0; i--) { $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete); } return; } this._onCloseComplete(result); function removeTriggerClass(el){ el.removeClass(self.options.triggerClass); } function onCloseComplete(){ if(self._open){ self._onCloseComplete(result); } } }; Dialog.prototype._getFadingElements = function(){ var elements = []; if(this.options.dialogFade){ elements.push(this.modalEl); } if(this.options.backdropFade){ elements.push(this.backdropEl); } return elements; }; Dialog.prototype._bindEvents = function() { if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); } if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); } }; Dialog.prototype._unbindEvents = function() { if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); } if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); } }; Dialog.prototype._onCloseComplete = function(result) { this._removeElementsFromDom(); this._unbindEvents(); this.deferred.resolve(result); }; Dialog.prototype._addElementsToDom = function(){ body.append(this.modalEl); if(this.options.backdrop) { if (activeBackdrops.value === 0) { body.append(this.backdropEl); } activeBackdrops.value++; } this._open = true; }; Dialog.prototype._removeElementsFromDom = function(){ this.modalEl.remove(); if(this.options.backdrop) { activeBackdrops.value--; if (activeBackdrops.value === 0) { this.backdropEl.remove(); } } this._open = false; }; // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog. Dialog.prototype._loadResolves = function(){ var values = [], keys = [], templatePromise, self = this; if (this.options.template) { templatePromise = $q.when(this.options.template); } else if (this.options.templateUrl) { templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache}) .then(function(response) { return response.data; }); } angular.forEach(this.options.resolve || [], function(value, key) { keys.push(key); values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value)); }); keys.push('$template'); values.push(templatePromise); return $q.all(values).then(function(values) { var locals = {}; angular.forEach(values, function(value, index) { locals[keys[index]] = value; }); locals.dialog = self; return locals; }); }; // The actual `$dialog` service that is injected in controllers. return { // Creates a new `Dialog` with the specified options. dialog: function(opts){ return new Dialog(opts); }, // creates a new `Dialog` tied to the default message box template and controller. // // Arguments `title` and `message` are rendered in the modal header and body sections respectively. // The `buttons` array holds an object with the following members for each button to include in the // modal footer section: // // * `result`: the result to pass to the `close` method of the dialog when the button is clicked // * `label`: the label of the button // * `cssClass`: additional css class(es) to apply to the button for styling messageBox: function(title, message, buttons){ return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve: {model: function() { return { title: title, message: message, buttons: buttons }; } }}); } }; }]; }); angular.module('ui.bootstrap.transition', []) /** * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. * @param {DOMElement} element The DOMElement that will be animated. * @param {string|object|function} trigger The thing that will cause the transition to start: * - As a string, it represents the css class to be added to the element. * - As an object, it represents a hash of style attributes to be applied to the element. * - As a function, it represents a function to be called that will cause the transition to occur. * @return {Promise} A promise that is resolved when the transition finishes. */ .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { var $transition = function(element, trigger, options) { options = options || {}; var deferred = $q.defer(); var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; var transitionEndHandler = function(event) { $rootScope.$apply(function() { element.unbind(endEventName, transitionEndHandler); deferred.resolve(element); }); }; if (endEventName) { element.bind(endEventName, transitionEndHandler); } // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur $timeout(function() { if ( angular.isString(trigger) ) { element.addClass(trigger); } else if ( angular.isFunction(trigger) ) { trigger(element); } else if ( angular.isObject(trigger) ) { element.css(trigger); } //If browser does not support transitions, instantly resolve if ( !endEventName ) { deferred.resolve(element); } }); // Add our custom cancel function to the promise that is returned // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, // i.e. it will therefore never raise a transitionEnd event for that transition deferred.promise.cancel = function() { if ( endEventName ) { element.unbind(endEventName, transitionEndHandler); } deferred.reject('Transition cancelled'); }; return deferred.promise; }; // Work out the name of the transitionEnd event var transElement = document.createElement('trans'); var transitionEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' }; var animationEndEventNames = { 'WebkitTransition': 'webkitAnimationEnd', 'MozTransition': 'animationend', 'OTransition': 'oAnimationEnd', 'msTransition': 'MSAnimationEnd', 'transition': 'animationend' }; function findEndEventName(endEventNames) { for (var name in endEventNames){ if (transElement.style[name] !== undefined) { return endEventNames[name]; } } } $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); $transition.animationEndEventName = findEndEventName(animationEndEventNames); return $transition; }]);