Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Commit

Permalink
feat(dialog): add dialog functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
typesafe authored and pkozlowski-opensource committed Jan 14, 2013
1 parent dc04547 commit 9c06f62
Show file tree
Hide file tree
Showing 8 changed files with 752 additions and 0 deletions.
92 changes: 92 additions & 0 deletions src/dialog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# $dialogProvider <small>(service in ui.bootstrap)</small>

## 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.
* `modalClass`: 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 nodal and backdrop, defaults to 'fade'
* `triggerClass`: the css class that triggers the transitions. default to 'in'
* `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
* `modalFade`: a boolean value indicating whether the nodal 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

Example:

var app = angular.module('App', ['ui.bootstrap.dialog'] , function($dialogProvider){
$dialogProvider.options({backdropClick: false, modalFade: true});
});

# $dialog service

## Description

Allows you to open dialogs from within you 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({modalFade: false, resolve: {item: 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');
};
}]);

#### `message(title, message, buttons)`

Opens a message box with the specified `title`, `message` ang 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`: optinal, 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.message('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.

267 changes: 267 additions & 0 deletions src/dialog/dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// 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,
modalClass: 'modal',
backdropClass: 'modal-backdrop',
transitionClass: 'fade',
triggerClass: 'in',
resolve:{},
backdropFade: false,
modalFade:false,
keyboard: true, // close with esc key
backdropClick: true // only in conjunction with backdrop=true
/* other options: template, templateUrl, controller */
};

var globalOptions = {};

// 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",
function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition) {

var body = $document.find('body');

function createElement(clazz) {
var el = angular.element("<div>");
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.backdropEl = createElement(options.backdropClass);
if(options.backdropFade){
this.backdropEl.addClass(options.transitionClass);
this.backdropEl.removeClass(options.triggerClass);
}

this.modalEl = createElement(options.modalClass);
if(options.modalFade){
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 = $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.contents())($scope);
self._addElementsToDom();

// trigger tranisitions
setTimeout(function(){
if(self.options.modalFade){ 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();

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.modalFade){
elements.push(this.modalEl);
}
if(this.options.backdropFade){
elements.push(this.backdropEl);
}

return elements;
};

Dialog.prototype._fadingEnabled = function(){
return this.options.modalFade || this.options.backdropFade;
};

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);

if(this._fadingEnabled()){
this.$scope.$apply();
}
};

Dialog.prototype._addElementsToDom = function(){
body.append(this.modalEl);
if(this.options.backdrop) { body.append(this.backdropEl); }
this._open = true;
};

Dialog.prototype._removeElementsFromDom = function(){
this.modalEl.remove();
if(this.options.backdrop) { 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 = [], template, self = this;

if (template = this.options.template) {
} else if (template = this.options.templateUrl) {
template = $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(value);
});

keys.push('$template');
values.push(template);

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: {
title: title,
message: message,
buttons: buttons
}}});
}
};
}];
});
Loading

0 comments on commit 9c06f62

Please sign in to comment.