Skip to content

Instantly share code, notes, and snippets.

@NathanGloyn
Last active December 19, 2015 02:28
Show Gist options
  • Save NathanGloyn/5882958 to your computer and use it in GitHub Desktop.
Save NathanGloyn/5882958 to your computer and use it in GitHub Desktop.
Files to create example of angular-ui dialog not clearing up scopes
var app = angular.module("app", ["ui.bootstrap.dialog"])
.config(function($routeProvider) {
$routeProvider.when("/",
{
templateUrl: "/App/Views/Home.html",
controller: "HomeController"
}
);
});
// 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',
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("<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._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();
};
this.handleLocationChange = function () {
self.close();
};
}
// 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); }
});
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();
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
};
}
}
});
}
};
}];
});
app.controller("HomeController", ["$scope", "$dialog", function ($scope, $dialog) {
var t = '<div>' +
'<button ng-click="close(result)" class="btn btn-primary" >Close</button>' +
'</div>';
$scope.opts = {
backdrop: true,
keyboard: true,
backdropClick: true,
template: t, //templateUrl: '/App/Views/TestDialog.html',
controller: 'TestDialogController'
};
$scope.openDialog = function () {
var d = $dialog.dialog($scope.opts);
d.open().then(function(result){
if(result)
{
console.log('dialog closed with result: ' + result);
}
});
};
}]);
<!DOCTYPE html >
<html lang="en" id="ng-app" ng-app="app">
<head>
<title></title>
<script src="Scripts/angular.js"></script>
<script src="Scripts/transition.js"></script>
<script src="Scripts/dialog.js"></script>
<script src="App/app.js"></script>
<script src="App/Controllers/Home.js"></script>
<script src="App/Controllers/TestDialog.js"></script>
<link href="Content/bootstrap.css" rel="stylesheet" />
</head>
<body>
<div ng-controller="HomeController">
<button class="btn" ng-click="openDialog()">dialog</button>
</div>
</body>
</html>
<div>
<button class="btn" ng-click="close('close')">close</button>
</div>
app.controller("TestDialogController", ["$scope", "dialog", function ($scope, dialog) {
$scope.close = function(result) {
dialog.close(result);
};
}]);
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',
'transition': 'transitionend'
};
var animationEndEventNames = {
'WebkitTransition': 'webkitAnimationEnd',
'MozTransition': 'animationend',
'OTransition': 'oAnimationEnd',
'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;
}]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment