diff --git a/src/dialog/README.md b/src/dialog/README.md
deleted file mode 100644
index d1c16236df..0000000000
--- a/src/dialog/README.md
+++ /dev/null
@@ -1,99 +0,0 @@
-# $dialogProvider (service in ui.bootstrap)
-
-## Description
-
-Used for configuring global options for dialogs.
-
-### Methods
-
-#### `options(opts)`
-
-Sets the default global options for your application. Options can be overridden when opening dialogs. Available options are:
-
-* `backdrop`: a boolean value indicating whether a backdrop should be used or not, defaults to true
-* `dialogClass`: the css class for the modal div, defaults to 'modal'
-* `backdropClass`: the css class for the backdrop, defaults to 'modal-backdrop'
-* `transitionClass`: the css class that applies transitions to the modal and backdrop, defaults to 'fade'
-* `triggerClass`: the css class that triggers the transitions, defaults to 'in'
-* `dialogOpenClass`: the css class that is added to body when dialog is opened, defaults to 'modal-open'
-* `resolve`: members that will be resolved and passed to the controller as locals
-* `controller`: the controller to associate with the included partial view
-* `backdropFade`: a boolean value indicating whether the backdrop should fade in and out using a CSS transition, defaults to false
-* `dialogFade`: a boolean value indicating whether the modal should fade in and out using a CSS transition, defaults to false
-* `keyboard`: indicates whether the dialog should be closable by hitting the ESC key, defaults to true
-* `backdropClick`: indicates whether the dialog should be closable by clicking the backdrop area, defaults to true
-* `template`: the template for dialog
-* `templateUrl`: path to the template for dialog
-
-Example:
-
- var app = angular.module('App', ['ui.bootstrap.dialog'] , function($dialogProvider){
- $dialogProvider.options({backdropClick: false, dialogFade: true});
- });
-
-# $dialog service
-
-## Description
-
-Allows you to open dialogs from within your controller.
-
-### Methods
-
-#### `dialog([templateUrl[, controller]])`
-
-Creates a new dialog, optionally setting the `templateUrl`, and `controller` options.
-
-Example:
-
- app.controller('MainCtrl', function($dialog, $scope) {
- $scope.openItemEditor = function(item){
- var d = $dialog.dialog({dialogFade: false, resolve: {item: function(){ return angular.copy(item); } }});
- d.open('dialogs/item-editor.html', 'EditItemController');
- };
- });
-
- // note that the resolved item as well as the dialog are injected in the dialog's controller
- app.controller('EditItemController', ['$scope', 'dialog', 'item', function($scope, dialog, item){
- $scope.item = item;
- $scope.submit = function(){
- dialog.close('ok');
- };
- }]);
-
-#### `messageBox(title, message, buttons)`
-
-Opens a message box with the specified `title`, `message` and a series of `buttons` can be provided, every button can specify:
-
-* `label`: the label of the button
-* `result`: the result used to invoke the close method of the dialog
-* `cssClass`: optional, the CSS class (e.g. btn-primary) to apply to the button
-
-Example:
-
- app.controller('MainCtrl', function($dialog, $scope) {
- $scope.deleteItem = function(item){
- var msgbox = $dialog.messageBox('Delete Item', 'Are you sure?', [{label:'Yes, I\'m sure', result: 'yes'},{label:'Nope', result: 'no'}]);
- msgbox.open().then(function(result){
- if(result === 'yes') {deleteItem(item);}
- });
- };
- });
-
-## Dialog class
-
-The dialog object returned by the `$dialog` service methods `open` and `message`.
-
-### Methods
-
-#### `open`
-
-(Re)Opens the dialog and returns a promise.
-
-#### `close([result])`
-
-Closes the dialog. Optionally a result can be specified. The result is used to resolve the promise returned by the `open` method.
-
-#### `isOpen`
-
-Returns true if the dialog is shown, else returns false.
-
diff --git a/src/dialog/dialog.js b/src/dialog/dialog.js
deleted file mode 100644
index 42d63f59e2..0000000000
--- a/src/dialog/dialog.js
+++ /dev/null
@@ -1,281 +0,0 @@
-// 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.children().data('ngControllerController', ctrl);
- }
-
- $compile(self.modalEl)($scope);
- self._addElementsToDom();
-
- // trigger tranisitions
- setTimeout(function(){
- if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
- if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
- });
- body.addClass(defaults.dialogOpenClass);
- 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();
-
- 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();
- body.removeClass(defaults.dialogOpenClass);
- 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
- };
- }
- }});
- }
- };
- }];
-});
diff --git a/src/dialog/docs/demo.html b/src/dialog/docs/demo.html
deleted file mode 100644
index b4e3cc8dcb..0000000000
--- a/src/dialog/docs/demo.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
Change options at will and press the open dialog button below!
-
-
-
Alternatively open a simple message box:
-
-
-
-
diff --git a/src/dialog/docs/demo.js b/src/dialog/docs/demo.js
deleted file mode 100644
index 6b9756c93b..0000000000
--- a/src/dialog/docs/demo.js
+++ /dev/null
@@ -1,50 +0,0 @@
-function DialogDemoCtrl($scope, $dialog){
-
- // Inlined template for demo
- var t = '
'+
- '
This is the title
'+
- '
'+
- '
'+
- '
Enter a value to pass to close as the result:
'+
- '
'+
- '';
-
- $scope.opts = {
- backdrop: true,
- keyboard: true,
- backdropClick: true,
- template: t, // OR: templateUrl: 'path/to/view.html',
- controller: 'TestDialogController'
- };
-
- $scope.openDialog = function(){
- var d = $dialog.dialog($scope.opts);
- d.open().then(function(result){
- if(result)
- {
- alert('dialog closed with result: ' + result);
- }
- });
- };
-
- $scope.openMessageBox = function(){
- var title = 'This is a message box';
- var msg = 'This is the content of the message box';
- var btns = [{result:'cancel', label: 'Cancel'}, {result:'ok', label: 'OK', cssClass: 'btn-primary'}];
-
- $dialog.messageBox(title, msg, btns)
- .open()
- .then(function(result){
- alert('dialog closed with result: ' + result);
- });
- };
-}
-
-// the dialog is injected in the specified controller
-function TestDialogController($scope, dialog){
- $scope.close = function(result){
- dialog.close(result);
- };
-}
diff --git a/src/dialog/docs/readme.md b/src/dialog/docs/readme.md
deleted file mode 100644
index e45f967a85..0000000000
--- a/src/dialog/docs/readme.md
+++ /dev/null
@@ -1,6 +0,0 @@
-The `$dialog` service allows you to open dialogs and message boxes from within your controllers. Very useful in case loading dialog content in the DOM up-front is tedious or not desired.
-
-Creating custom dialogs is straightforward: create a partial view, its controller and reference them when using the service.
-Generic message boxes (title, message and buttons) are also provided for your convenience.
-
-For more information, see the [dialog readme](https://github.com/angular-ui/bootstrap/blob/master/src/dialog/README.md) on github.
\ No newline at end of file
diff --git a/src/dialog/test/dialog.spec.js b/src/dialog/test/dialog.spec.js
deleted file mode 100644
index 1998bf0466..0000000000
--- a/src/dialog/test/dialog.spec.js
+++ /dev/null
@@ -1,309 +0,0 @@
-describe('Given ui.bootstrap.dialog', function(){
-
- var $document, $compile, $scope, $rootScope, $dialog, q, provider;
- var template = '
I\'m a template
';
-
- beforeEach(module('ui.bootstrap.dialog'));
- beforeEach(module('template/dialog/message.html'));
-
- beforeEach(function(){
- module(function($dialogProvider){
- provider = $dialogProvider;
- });
- inject(function(_$document_, _$compile_, _$rootScope_, _$dialog_, _$q_){
- $document = _$document_;
- $compile = _$compile_;
- $scope = _$rootScope_.$new();
- $rootScope = _$rootScope_;
- $dialog = _$dialog_;
- q = _$q_;
- });
- });
-
- // clean-up after ourselves
- afterEach(function(){
- closeDialog();
- clearGlobalOptions();
- });
-
- it('provider service should be injected', function(){
- expect(provider).toBeDefined();
- });
-
- it('dialog service should be injected', function(){
- expect($dialog).toBeDefined();
- });
-
- var dialog;
-
- var createDialog = function(opts){
- dialog = $dialog.dialog(opts);
- };
-
- var openDialog = function(templateUrl, controller){
- dialog.open(templateUrl, controller);
- $scope.$apply();
- };
-
- var closeDialog = function(result){
- if(dialog){
- dialog.close(result);
- $rootScope.$apply();
- }
- };
-
- var setGlobalOptions = function(opts){
- provider.options(opts);
- };
-
- var clearGlobalOptions = function(){
- provider.options({});
- };
-
-
- var dialogShouldBeClosed = function(){
- it('should not include a backdrop in the DOM', function(){
- expect($document.find('body > div.modal-backdrop').length).toBe(0);
- });
-
- it('should not include the modal in the DOM', function(){
- expect($document.find('body > div.modal').length).toBe(0);
- });
-
- it('should return false for isOpen()', function(){
- expect(dialog.isOpen()).toBeFalsy();
- });
- };
-
- var dialogShouldBeOpen = function(){
- it('the dialog.isOpen() should be true', function(){
- expect(dialog.isOpen()).toBe(true);
- });
-
- it('the backdrop should be displayed', function(){
- expect($document.find('body > div.modal-backdrop').css('display')).toBe('block');
- });
-
- it('the modal should be displayed', function(){
- expect($document.find('body > div.modal').css('display')).toBe('block');
- });
- };
-
- describe('Given global option', function(){
-
- var useDialogWithGlobalOption = function(opts){
- beforeEach(function(){
- setGlobalOptions(opts);
- createDialog({template:template});
- openDialog();
- });
- };
-
- describe('backdrop:false', function(){
- useDialogWithGlobalOption({backdrop: false});
-
- it('should not include a backdrop in the DOM', function(){
- expect($document.find('body > div.modal-backdrop').length).toBe(0);
- });
-
- it('should include the modal in the DOM', function(){
- expect($document.find('body > div.modal').length).toBe(1);
- });
- });
-
- describe('dialogClass:foo, backdropClass:bar', function(){
- useDialogWithGlobalOption({dialogClass: 'foo', backdropClass: 'bar'});
-
- it('backdrop class should be changed', function(){
- expect($document.find('body > div.bar').length).toBe(1);
- });
-
- it('the modal should be change', function(){
- expect($document.find('body > div.foo').length).toBe(1);
- });
- });
-
- /*
- describe('dialogFade:true, backdropFade:true', function(){
- useDialogWithGlobalOption({dialogFade:true, backdropFade:true});
-
- it('backdrop class should be changed', function(){
- expect($document.find('body > div.modal.fade').length).toBe(1);
- });
-
- it('the modal should be change', function(){
- expect($document.find('body > div.modal-backdrop.fade').length).toBe(1);
- });
- });*/
- });
-
- describe('Opening a dialog', function(){
-
- beforeEach(function(){
- createDialog({template:template});
- openDialog();
- });
-
- dialogShouldBeOpen();
- });
-
- describe('When opening a dialog with a controller', function(){
-
- var resolvedDialog;
- function Ctrl(dialog){
- resolvedDialog = dialog;
- }
-
- beforeEach(function(){
- createDialog({template:template, controller: Ctrl});
- openDialog();
- });
-
- dialogShouldBeOpen();
-
- it('should inject the current dialog in the controller', function(){
- expect(resolvedDialog).toBe(dialog);
- });
- });
-
- describe('When opening a dialog with resolves', function(){
-
- var resolvedFoo, resolvedBar, deferred, resolveObj;
- function Ctrl(foo, bar){
- resolvedFoo = foo;
- resolvedBar = bar;
- }
-
- beforeEach(function(){
- deferred = q.defer();
- resolveObj = {
- foo: function(){return 'foo';},
- bar: function(){return deferred.promise;}
- };
-
- createDialog({template:template, resolve: resolveObj, controller: Ctrl});
- deferred.resolve('bar');
- openDialog();
- });
-
- dialogShouldBeOpen();
-
- it('should inject resolved promises in the controller', function(){
- expect(resolvedBar).toBe('bar');
- });
-
- it('should inject simple values in the controller', function(){
- expect(resolvedFoo).toBe('foo');
- });
- });
-
- describe('when closing a dialog', function(){
-
- beforeEach(function(){
- createDialog({template:template});
- openDialog();
- closeDialog();
- });
-
- dialogShouldBeClosed();
-
- describe('When opening it again', function(){
- beforeEach(function(){
- expect($document.find('body > div.modal-backdrop').length).toBe(0);
- openDialog();
- });
-
- dialogShouldBeOpen();
- });
- });
-
- describe('when closing a dialog with a result', function(){
- var res;
- beforeEach(function(){
- createDialog({template:template});
- dialog.open().then(function(result){ res = result; });
- $rootScope.$apply();
-
- closeDialog('the result');
- });
-
- dialogShouldBeClosed();
-
- it('should call the then method with the specified result', function(){
- expect(res).toBe('the result');
- });
- });
-
- describe('when closing a dialog with backdrop click', function(){
- beforeEach(function(){
- createDialog({template:'foo'});
- openDialog();
- $document.find('body > div.modal-backdrop').click();
- });
-
- dialogShouldBeClosed();
- });
-
- describe('when closing a dialog with escape key', function(){
- beforeEach(function(){
- createDialog({template:'foo'});
- openDialog();
- var e = $.Event('keydown');
- e.which = 27;
- $document.find('body').trigger(e);
- });
-
- dialogShouldBeClosed();
- });
-
- describe('When opening a dialog with a template url', function(){
-
- beforeEach(function(){
- createDialog({templateUrl:'template/dialog/message.html'});
- openDialog();
- });
-
- dialogShouldBeOpen();
- });
-
- describe('When opening a dialog by passing template and controller to open method', function(){
-
- var controllerIsCreated;
- function Controller($scope, dialog){
- controllerIsCreated = true;
- }
-
- beforeEach(function(){
- createDialog({templateUrl:'this/will/not/be/used.html', controller: 'foo'});
- openDialog('template/dialog/message.html', Controller);
- });
-
- dialogShouldBeOpen();
-
- it('should used the specified controller', function(){
- expect(controllerIsCreated).toBe(true);
- });
-
- it('should use the specified template', function(){
- expect($document.find('body > div.modal > div.modal-header').length).toBe(1);
- });
- });
-
- describe('when opening it with a template containing white-space', function(){
-
- var controllerIsCreated;
- function Controller($scope, dialog){
- controllerIsCreated = true;
- }
-
- beforeEach(function(){
- createDialog({
- template:'
Has whitespace that IE8 does not like assigning data() to
\ No newline at end of file
diff --git a/src/modal/docs/demo.js b/src/modal/docs/demo.js
index ff307fea8c..0eb06c330b 100644
--- a/src/modal/docs/demo.js
+++ b/src/modal/docs/demo.js
@@ -1,19 +1,39 @@
-var ModalDemoCtrl = function ($scope) {
+var ModalDemoCtrl = function ($scope, $modal, $log) {
+
+ $scope.items = ['item1', 'item2', 'item3'];
$scope.open = function () {
- $scope.shouldBeOpen = true;
- };
- $scope.close = function () {
- $scope.closeMsg = 'I was closed at: ' + new Date();
- $scope.shouldBeOpen = false;
+ var modalInstance = $modal.open({
+ templateUrl: 'myModalContent.html',
+ controller: ModalInstanceCtrl,
+ resolve: {
+ items: function () {
+ return $scope.items;
+ }
+ }
+ });
+
+ modalInstance.result.then(function (selectedItem) {
+ $scope.selected = selectedItem;
+ }, function () {
+ $log.info('Modal dismissed at: ' + new Date());
+ });
};
+};
- $scope.items = ['item1', 'item2'];
+var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
- $scope.opts = {
- backdropFade: true,
- dialogFade:true
+ $scope.items = items;
+ $scope.selected = {
+ item: $scope.items[0]
};
+ $scope.ok = function () {
+ $modalInstance.close($scope.selected.item);
+ };
+
+ $scope.cancel = function () {
+ $modalInstance.dismiss('cancel');
+ };
};
\ No newline at end of file
diff --git a/src/modal/docs/readme.md b/src/modal/docs/readme.md
index f72d844c57..697a587cfd 100644
--- a/src/modal/docs/readme.md
+++ b/src/modal/docs/readme.md
@@ -1,5 +1,18 @@
-`modal` is a directive that reuses `$dialog` service to provide simple creation of modals that are already in your DOM without the hassle of creating partial views and controllers.
+`$modal` is a s service to quickly create AngularJS-powered modal windows.
+Creating custom modals is straightforward: create a partial view, its controller and reference them when using the service.
-The directive shares `$dialog` global options.
+The `$modal` service has only one method: `open(options)` where available options are like follows:
-For more information, see the [dialog readme](https://github.com/angular-ui/bootstrap/blob/master/src/dialog/README.md) on github.
+* `templateUrl` - a path to a template representing modal's content
+* `scope` - a scope instance to be used for the modal's content (actually the `$modal` service is going to create a child scope of a a provided scope). Defaults to `$rootScope`
+* `controller` - a controller for a modal instance - it can initialize scope used by modal. A controller can be injected with `$modalInstance`
+* `resolve` - members that will be resolved and passed to the controller as locals; it is equivalent of the `resolve` property for AngularJS routes
+* `backdrop` - controls presence of a backdrop. Allowed values: true (default), false (no backdrop), `'static'` - backdrop is present but modal window is not closed when clicking outside of the modal window.
+* `keyboard` - indicates whether the dialog should be closable by hitting the ESC key, defaults to true
+
+The `open` method returns a modal instance, an object with the following properties:
+
+* `close(result)` - a method that can be used to close a modal, passing a result
+* `dismiss(reason)` - a method that can be used to dismiss a modal, passing a reason
+* `result` - a promise that is resolved when a modal is closed and rejected when a modal is dismissed
+* `opened` - a promise that is resolved when a modal gets opened after downloading content's template and resolving all variables
\ No newline at end of file
diff --git a/src/modal/modal.js b/src/modal/modal.js
index 7f8a828a6d..11a5424152 100644
--- a/src/modal/modal.js
+++ b/src/modal/modal.js
@@ -1,47 +1,287 @@
-angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
-.directive('modal', ['$parse', '$dialog', function($parse, $dialog) {
- return {
- restrict: 'EA',
- terminal: true,
- link: function(scope, elm, attrs) {
- var opts = angular.extend({}, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options));
- var shownExpr = attrs.modal || attrs.show;
- var setClosed;
-
- // Create a dialog with the template as the contents of the directive
- // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope
- opts = angular.extend(opts, {
- template: elm.html(),
- resolve: { $scope: function() { return scope; } }
- });
- var dialog = $dialog.dialog(opts);
+angular.module('ui.bootstrap.modal', [])
- elm.remove();
+/**
+ * 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 = [];
- if (attrs.close) {
- setClosed = function() {
- $parse(attrs.close)(scope);
+ 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];
+ }
+ }
+ },
+ 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;
+ }
};
- } else {
- setClosed = function() {
- if (angular.isFunction($parse(shownExpr).assign)) {
- $parse(shownExpr).assign(scope, false);
+ }
+ };
+ })
+
+/**
+ * A helper directive for the $modal service. It creates a backdrop element.
+ */
+ .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
+ return {
+ restrict: 'EA',
+ scope: {},
+ 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();
+ //TODO: this logic is duplicated with the place where modal gets opened
+ if (modal && modal.window.backdrop && modal.window.backdrop != 'static') {
+ evt.preventDefault();
+ evt.stopPropagation();
+ $modalStack.dismiss(modal.instance, 'backdrop click');
}
};
}
+ };
+ }])
+
+ .directive('modalWindow', ['$timeout', function ($timeout) {
+ return {
+ restrict: 'EA',
+ scope: {},
+ replace: true,
+ transclude: true,
+ templateUrl: 'template/modal/window.html',
+ link: function (scope, element, attrs) {
+ //trigger CSS transitions
+ $timeout(function () {
+ scope.animate = true;
+ });
+ }
+ };
+ }])
+
+ .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap',
+ function ($document, $compile, $rootScope, $$stackedMap) {
+
+ var body = $document.find('body').eq(0);
+ var openedWindows = $$stackedMap.createNew();
+ var $modalStack = {};
+
+ function removeModalWindow(modalInstance) {
+
+ var modalWindow = openedWindows.get(modalInstance).value;
+
+ //clean up the stack
+ openedWindows.remove(modalInstance);
+
+ //remove DOM element
+ modalWindow.modalDomEl.remove();
+
+ //remove backdrop
+ if (modalWindow.backdropDomEl) {
+ modalWindow.backdropDomEl.remove();
+ }
+
+ //destroy scope
+ modalWindow.modalScope.$destroy();
+ }
- scope.$watch(shownExpr, function(isShown, oldShown) {
- if (isShown) {
- dialog.open().then(function(){
- setClosed();
- });
- } else {
- //Make sure it is not opened
- if (dialog.isOpen()){
- dialog.close();
+ $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);
+ });
}
}
});
- }
- };
-}]);
\ No newline at end of file
+
+ $modalStack.open = function (modalInstance, modal) {
+
+ var backdropDomEl;
+ var modalDomEl = $compile(angular.element('').html(modal.content))(modal.scope);
+ body.append(modalDomEl);
+
+ if (modal.backdrop) {
+ backdropDomEl = $compile(angular.element(''))($rootScope);
+ body.append(backdropDomEl);
+ }
+
+ openedWindows.add(modalInstance, {
+ deferred: modal.deferred,
+ modalScope: modal.scope,
+ modalDomEl: modalDomEl,
+ backdrop: modal.backdrop,
+ backdropDomEl: backdropDomEl,
+ keyboard: modal.keyboard
+ });
+ };
+
+ $modalStack.close = function (modalInstance, result) {
+ var modal = openedWindows.get(modalInstance);
+ if (modal) {
+ modal.value.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.getTop = function () {
+ var top = openedWindows.top();
+ if (top) {
+ return {
+ instance: top.key,
+ window: top.value
+ };
+ }
+ };
+
+ 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);
+ }
+ };
+
+ //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
+ });
+
+ }, function resolveError(reason) {
+ modalResultDeferred.reject(reason);
+ });
+
+ templateAndResolvePromise.then(function () {
+ modalOpenedDeferred.resolve(true);
+ }, function () {
+ modalOpenedDeferred.reject(false);
+ });
+
+ return modalInstance;
+ };
+
+ return $modal;
+ }]
+ };
+ });
\ No newline at end of file
diff --git a/src/modal/test/modal.spec.js b/src/modal/test/modal.spec.js
index e212b0e14a..0182a68058 100644
--- a/src/modal/test/modal.spec.js
+++ b/src/modal/test/modal.spec.js
@@ -1,171 +1,403 @@
-describe('Give ui.boostrap.modal', function() {
-
- var $document, $compile, $scope, $rootScope, provider;
-
- beforeEach(module('ui.bootstrap.modal'));
-
- beforeEach(function(){
- module(function($dialogProvider){
- provider = $dialogProvider;
- });
- inject(function(_$document_, _$compile_, _$rootScope_){
- $document = _$document_;
- $compile = _$compile_;
- $scope = _$rootScope_.$new();
- $rootScope = _$rootScope_;
- });
- });
-
- var elm;
-
- var templateGenerator = function(expr, scopeExpressionContent, closeExpr) {
- var additionalExpression = scopeExpressionContent ? scopeExpressionContent : '';
- var closingExpr = closeExpr ? ' close="' + closeExpr + '" ': '';
- return '
' +
- additionalExpression + 'Hello!
';
- };
-
- it('should have just one backdrop', function() {
- var numberOfSimultaneousModals = 5;
- var elems = [];
- for (var i = 0; i< 5; i++) {
- elems[i] = $compile(templateGenerator('modalShown' + i))($scope);
- $scope.$apply('modalShown' + i + ' = true');
- }
- expect($document.find('body > div.modal-backdrop').length).toBe(1);
- expect($document.find('body > div.modal').length).toBe(numberOfSimultaneousModals);
-
- for (i = 0; i< 5; i++) {
- $scope.$apply('modalShown' + i + ' = false');
- }
- });
-
- it('should work with expression instead of a variable', function() {
- $scope.foo = true;
- $scope.shown = function() { return $scope.foo; };
- elm = $compile(templateGenerator('shown()'))($scope);
- $scope.$apply();
- expect($document.find('body > div.modal').length).toBe(1);
- $scope.$apply('foo = false');
- expect($document.find('body > div.modal').length).toBe(0);
- });
-
- it('should work with a close expression and escape close', function() {
- $scope.bar = true;
- $scope.show = function() { return $scope.bar; };
- elm = $compile(templateGenerator('show()', ' ', 'bar=false'))($scope);
- $scope.$apply();
- expect($document.find('body > div.modal').length).toBe(1);
- var e = $.Event('keydown');
- e.which = 27;
- $document.find('body').trigger(e);
- expect($document.find('body > div.modal').length).toBe(0);
- expect($scope.bar).not.toBeTruthy();
- });
-
- it('should work with a close expression and backdrop close', function() {
- $scope.baz = 1;
- $scope.hello = function() { return $scope.baz===1; };
- elm = $compile(templateGenerator('hello()', ' ', 'baz=0'))($scope);
- $scope.$apply();
- expect($document.find('body > div.modal').length).toBe(1);
- $document.find('body > div.modal-backdrop').click();
- expect($document.find('body > div.modal').length).toBe(0);
- expect($scope.baz).toBe(0);
- });
-
- it('should not close on escape if option is false', function() {
- $scope.modalOpts = {keyboard:false};
- elm = $compile(templateGenerator('modalShown'))($scope);
- $scope.modalShown = true;
- $scope.$apply();
- var e = $.Event('keydown');
- e.which = 27;
- expect($document.find('body > div.modal').length).toBe(1);
- $document.find('body').trigger(e);
- expect($document.find('body > div.modal').length).toBe(1);
- $scope.$apply('modalShown = false');
- });
-
- it('should not close on backdrop click if option is false', function() {
- $scope.modalOpts = {backdropClick:false};
- elm = $compile(templateGenerator('modalShown'))($scope);
- $scope.modalShown = true;
- $scope.$apply();
- expect($document.find('body > div.modal').length).toBe(1);
- $document.find('body > div.modal-backdrop').click();
- expect($document.find('body > div.modal').length).toBe(1);
- $scope.$apply('modalShown = false');
- });
-
- it('should use global $dialog options', function() {
- elm = $compile(templateGenerator('modalShown'))($scope);
- expect($document.find('.test-open-modal').length).toBe(0);
- $scope.$apply('modalShown = true');
- expect($document.find('body > div.modal').length).toBe(1);
- $scope.$apply('modalShown = false');
- });
-
- describe('dialog generated should have directives scope', function() {
-
- afterEach(function() {
- $scope.$apply('modalShown = false');
- });
-
- it('should call scope methods', function() {
- var clickSpy = jasmine.createSpy('localScopeFunction');
- $scope.myFunc = clickSpy;
- elm = $compile(templateGenerator('modalShown', ''))($scope);
- $scope.$apply('modalShown = true');
- $document.find('body > div.modal button').click();
- expect(clickSpy).toHaveBeenCalled();
- });
-
- it('should resolve scope vars', function() {
- $scope.buttonName = 'my button';
- elm = $compile(templateGenerator('modalShown', ''))($scope);
- $scope.$apply('modalShown = true');
- expect($document.find('body > div.modal button').text()).toBe('my button');
- });
-
- });
-
- describe('toogle modal dialog on model change', function() {
-
- beforeEach(function(){
- elm = $compile(templateGenerator('modalShown'))($scope);
- $scope.$apply('modalShown = true');
- });
-
- afterEach(function() {
- $scope.$apply('modalShown = false');
- });
-
- it('the backdrop should be displayed if specified (true by default)', function(){
- expect($document.find('body > div.modal-backdrop').css('display')).toBe('block');
- });
-
- it('the modal should be displayed', function(){
- expect($document.find('body > div.modal').css('display')).toBe('block');
- });
-
- it('the modal should not be displayed', function(){
- $scope.$apply('modalShown = false');
- expect($document.find('body > div.modal').length).toBe(0);
- });
-
- it('should update the model if the backdrop is clicked', function() {
- $document.find('body > div.modal-backdrop').click();
- $scope.$digest();
- expect($scope.modalShown).not.toBeTruthy();
- });
-
- it('should update the model if the esc is pressed', function() {
- var e = $.Event('keydown');
- e.which = 27;
- $document.find('body').trigger(e);
- $scope.$digest();
- expect($scope.modalShown).not.toBeTruthy();
- });
- });
+describe('$modal', function () {
+ var $rootScope, $document, $compile, $templateCache, $timeout, $q;
+ var $modal, $modalProvider;
+
+ var triggerKeyDown = function (element, keyCode) {
+ var e = $.Event("keydown");
+ e.which = keyCode;
+ element.trigger(e);
+ };
+
+ beforeEach(module('ui.bootstrap.modal'));
+ beforeEach(module('template/modal/backdrop.html'));
+ beforeEach(module('template/modal/window.html'));
+ beforeEach(module(function(_$modalProvider_){
+ $modalProvider = _$modalProvider_;
+ }));
+
+ beforeEach(inject(function (_$rootScope_, _$document_, _$compile_, _$templateCache_, _$timeout_, _$q_, _$modal_) {
+ $rootScope = _$rootScope_;
+ $document = _$document_;
+ $compile = _$compile_;
+ $templateCache = _$templateCache_;
+ $timeout = _$timeout_;
+ $q = _$q_;
+ $modal = _$modal_;
+ }));
+
+ beforeEach(inject(function ($rootScope) {
+ this.addMatchers({
+
+ toBeResolvedWith: function(value) {
+ var resolved;
+ this.message = function() {
+ return "Expected '" + angular.mock.dump(this.actual) + "' to be resolved with '" + value + "'.";
+ };
+ this.actual.then(function(result){
+ resolved = result;
+ });
+ $rootScope.$digest();
+
+ return resolved === value;
+ },
+
+ toBeRejectedWith: function(value) {
+ var rejected;
+ this.message = function() {
+ return "Expected '" + angular.mock.dump(this.actual) + "' to be rejected with '" + value + "'.";
+ };
+ this.actual.then(angular.noop, function(reason){
+ rejected = reason;
+ });
+ $rootScope.$digest();
+
+ return rejected === value;
+ },
+
+ toHaveModalOpenWithContent: function(content, selector) {
+
+ var contentToCompare, modalDomEls = this.actual.find('body > div.modal');
+
+ this.message = function() {
+ return "Expected '" + angular.mock.dump(modalDomEls) + "' to be open with '" + content + "'.";
+ };
+
+ contentToCompare = selector ? modalDomEls.find(selector) : modalDomEls;
+ return modalDomEls.css('display') === 'block' && contentToCompare.html() == content;
+ },
+
+ toHaveModalsOpen: function(noOfModals) {
+
+ var modalDomEls = this.actual.find('body > div.modal');
+ return modalDomEls.length === noOfModals;
+ },
+
+ toHaveBackdrop: function() {
+
+ var backdropDomEls = this.actual.find('body > div.modal-backdrop');
+ this.message = function() {
+ return "Expected '" + angular.mock.dump(backdropDomEls) + "' to be a backdrop element'.";
+ };
+
+ return backdropDomEls.length === 1;
+ }
+ });
+ }));
+
+ afterEach(function () {
+ var body = $document.find('body');
+ body.find('div.modal').remove();
+ body.find('div.modal-backdrop').remove();
+ });
+
+ function open(modalOptions) {
+ var modal = $modal.open(modalOptions);
+ $rootScope.$digest();
+ return modal;
+ }
+
+ function close(modal, result) {
+ modal.close(result);
+ $rootScope.$digest();
+ }
+
+ function dismiss(modal, reason) {
+ modal.dismiss(reason);
+ $rootScope.$digest();
+ }
+
+ describe('basic scenarios with default options', function () {
+
+ it('should open and dismiss a modal with a minimal set of options', function () {
+
+ var modal = open({template: '
Content
'});
+
+ expect($document).toHaveModalsOpen(1);
+ expect($document).toHaveModalOpenWithContent('Content', 'div');
+ expect($document).toHaveBackdrop();
+
+ dismiss(modal, 'closing in test');
+
+ expect($document).toHaveModalsOpen(0);
+ expect($document).not.toHaveBackdrop();
+ });
+
+ it('should open a modal from templateUrl', function () {
+
+ $templateCache.put('content.html', '
URL Content
');
+ var modal = open({templateUrl: 'content.html'});
+
+ expect($document).toHaveModalsOpen(1);
+ expect($document).toHaveModalOpenWithContent('URL Content', 'div');
+ expect($document).toHaveBackdrop();
+
+ dismiss(modal, 'closing in test');
+
+ expect($document).toHaveModalsOpen(0);
+ expect($document).not.toHaveBackdrop();
+ });
+
+ it('should support closing on ESC', function () {
+
+ var modal = open({template: '
Content
'});
+ expect($document).toHaveModalsOpen(1);
+
+ triggerKeyDown($document, 27);
+ $rootScope.$digest();
+
+ expect($document).toHaveModalsOpen(0);
+ });
+
+ it('should support closing on backdrop click', function () {
+
+ var modal = open({template: '
Content
'});
+ expect($document).toHaveModalsOpen(1);
+
+ $document.find('body > div.modal-backdrop').click();
+ $rootScope.$digest();
+
+ expect($document).toHaveModalsOpen(0);
+ });
+
+ it('should resolve returned promise on close', function () {
+ var modal = open({template: '
Content
'});
+ close(modal, 'closed ok');
+
+ expect(modal.result).toBeResolvedWith('closed ok');
+ });
+
+ it('should reject returned promise on dismiss', function () {
+
+ var modal = open({template: '
Content
'});
+ dismiss(modal, 'esc');
+
+ expect(modal.result).toBeRejectedWith('esc');
+ });
+
+ it('should expose a promise linked to the templateUrl / resolve promises', function () {
+ var modal = open({template: '
Content
', resolve: {
+ ok: function() {return $q.when('ok');}
+ }}
+ );
+ expect(modal.opened).toBeResolvedWith(true);
+ });
+
+ it('should expose a promise linked to the templateUrl / resolve promises and reject it if needed', function () {
+ var modal = open({template: '
Content
', resolve: {
+ ok: function() {return $q.reject('ko');}
+ }}
+ );
+ expect(modal.opened).toBeRejectedWith(false);
+ });
+
+ });
+
+ describe('default options can be changed in a provider', function () {
+
+ it('should allow overriding default options in a provider', function () {
+
+ $modalProvider.options.backdrop = false;
+ var modal = open({template: '
Content
'});
+
+ expect($document).toHaveModalOpenWithContent('Content', 'div');
+ expect($document).not.toHaveBackdrop();
+ });
+ });
+
+ describe('option by option', function () {
+
+ describe('template and templateUrl', function () {
+
+ it('should throw an error if none of template and templateUrl are provided', function () {
+ expect(function(){
+ var modal = open({});
+ }).toThrow(new Error('One of template or templateUrl options is required.'));
+ });
+
+ it('should not fail if a templateUrl contains leading / trailing white spaces', function () {
+
+ $templateCache.put('whitespace.html', '
Whitespaces
');
+ open({templateUrl: 'whitespace.html'});
+ expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
+ });
+
+ });
+
+ describe('controllers', function () {
+
+ it('should accept controllers and inject modal instances', function () {
+
+ var TestCtrl = function($scope, $modalInstance) {
+ $scope.fromCtrl = 'Content from ctrl';
+ $scope.isModalInstance = angular.isObject($modalInstance) && angular.isFunction($modalInstance.close);
+ };
+
+ var modal = open({template: '
', {
+ value: function () {
+ return 'Content from resolve';
+ }
+ }));
+
+ expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
+ });
+
+ it('should delay showing modal if one of the resolves is a promise', function () {
+
+ open(modalDefinition('
{{value}}
', {
+ value: function () {
+ return $timeout(function(){ return 'Promise'; }, 100);
+ }
+ }));
+ expect($document).toHaveModalsOpen(0);
+
+ $timeout.flush();
+ expect($document).toHaveModalOpenWithContent('Promise', 'div');
+ });
+
+ it('should not open dialog (and reject returned promise) if one of resolve fails', function () {
+
+ var deferred = $q.defer();
+
+ var modal = open(modalDefinition('
{{value}}
', {
+ value: function () {
+ return deferred.promise;
+ }
+ }));
+ expect($document).toHaveModalsOpen(0);
+
+ deferred.reject('error in test');
+ $rootScope.$digest();
+
+ expect($document).toHaveModalsOpen(0);
+ expect(modal.result).toBeRejectedWith('error in test');
+ });
+
+ it('should support injection with minification-safe syntax in resolve functions', function () {
+
+ open(modalDefinition('
{{value.id}}
', {
+ value: ['$locale', function (e) {
+ return e;
+ }]
+ }));
+
+ expect($document).toHaveModalOpenWithContent('en-us', 'div');
+ });
+
+ //TODO: resolves with dependency injection - do we want to support them?
+ });
+
+ describe('scope', function () {
+
+ it('should custom scope if provided', function () {
+ var $scope = $rootScope.$new();
+ $scope.fromScope = 'Content from custom scope';
+ open({
+ template: '
{{fromScope}}
',
+ scope: $scope
+ });
+ expect($document).toHaveModalOpenWithContent('Content from custom scope', 'div');
+ });
+ });
+
+ describe('keyboard', function () {
+
+ it('should not close modals if keyboard option is set to false', function () {
+ open({
+ template: '
No keyboard
',
+ keyboard: false
+ });
+
+ expect($document).toHaveModalsOpen(1);
+
+ triggerKeyDown($document, 27);
+ $rootScope.$digest();
+
+ expect($document).toHaveModalsOpen(1);
+ });
+ });
+
+ describe('backdrop', function () {
+
+ it('should not have any backdrop element if backdrop set to false', function () {
+ open({
+ template: '
No backdrop
',
+ backdrop: false
+ });
+ expect($document).toHaveModalOpenWithContent('No backdrop', 'div');
+ expect($document).not.toHaveBackdrop();
+ });
+
+ it('should not close modal on backdrop click if backdrop is specified as "static"', function () {
+ open({
+ template: '
'});
+ expect($document).toHaveModalsOpen(2);
+
+ dismiss(modal2);
+ expect($document).toHaveModalsOpen(1);
+ expect($document).toHaveModalOpenWithContent('Modal1', 'div');
+
+ dismiss(modal1);
+ expect($document).toHaveModalsOpen(0);
+ });
+
+ it('should not close any modals on ESC if the topmost one does not allow it', function () {
+
+ var modal1 = open({template: '
Modal1
'});
+ var modal2 = open({template: '
Modal2
', keyboard: false});
+
+ triggerKeyDown($document, 27);
+ $rootScope.$digest();
+
+ expect($document).toHaveModalsOpen(2);
+ });
+
+ it('should not close any modals on click if a topmost modal does not have backdrop', function () {
+
+ var modal1 = open({template: '
Modal1
'});
+ var modal2 = open({template: '
Modal2
', backdrop: false});
+
+ $document.find('body > div.modal-backdrop').click();
+ $rootScope.$digest();
+
+ expect($document).toHaveModalsOpen(2);
+ });
+ });
});
\ No newline at end of file
diff --git a/src/modal/test/stackedMap.spec.js b/src/modal/test/stackedMap.spec.js
new file mode 100644
index 0000000000..d1a9d68cfe
--- /dev/null
+++ b/src/modal/test/stackedMap.spec.js
@@ -0,0 +1,50 @@
+describe('stacked map', function () {
+
+ var stackedMap;
+
+ beforeEach(module('ui.bootstrap.modal'));
+ beforeEach(inject(function ($$stackedMap) {
+ stackedMap = $$stackedMap.createNew();
+ }));
+
+ it('should add and remove objects by key', function () {
+
+ stackedMap.add('foo', 'foo_value');
+ expect(stackedMap.length()).toEqual(1);
+ expect(stackedMap.get('foo').key).toEqual('foo');
+ expect(stackedMap.get('foo').value).toEqual('foo_value');
+
+ stackedMap.remove('foo');
+ expect(stackedMap.length()).toEqual(0);
+ expect(stackedMap.get('foo')).toBeUndefined();
+ });
+
+ it('should get topmost element', function () {
+
+ stackedMap.add('foo', 'foo_value');
+ stackedMap.add('bar', 'bar_value');
+ expect(stackedMap.length()).toEqual(2);
+
+ expect(stackedMap.top().key).toEqual('bar');
+ expect(stackedMap.length()).toEqual(2);
+ });
+
+ it('should remove topmost element', function () {
+
+ stackedMap.add('foo', 'foo_value');
+ stackedMap.add('bar', 'bar_value');
+
+ expect(stackedMap.removeTop().key).toEqual('bar');
+ expect(stackedMap.removeTop().key).toEqual('foo');
+ });
+
+ it('should preserve semantic of an empty stackedMap', function () {
+
+ expect(stackedMap.length()).toEqual(0);
+ expect(stackedMap.top()).toBeUndefined();
+ });
+
+ it('should ignore removal of non-existing elements', function () {
+ expect(stackedMap.remove('non-existing')).toBeUndefined();
+ });
+});
\ No newline at end of file
diff --git a/template/modal/backdrop.html b/template/modal/backdrop.html
new file mode 100644
index 0000000000..da5d62ad54
--- /dev/null
+++ b/template/modal/backdrop.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/template/modal/window.html b/template/modal/window.html
new file mode 100644
index 0000000000..e83cf51c75
--- /dev/null
+++ b/template/modal/window.html
@@ -0,0 +1 @@
+
\ No newline at end of file