Last active
December 19, 2015 02:28
-
-
Save NathanGloyn/5882958 to your computer and use it in GitHub Desktop.
Files to create example of angular-ui dialog not clearing up scopes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var app = angular.module("app", ["ui.bootstrap.dialog"]) | |
.config(function($routeProvider) { | |
$routeProvider.when("/", | |
{ | |
templateUrl: "/App/Views/Home.html", | |
controller: "HomeController" | |
} | |
); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
}; | |
} | |
} | |
}); | |
} | |
}; | |
}]; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
}); | |
}; | |
}]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<button class="btn" ng-click="close('close')">close</button> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.controller("TestDialogController", ["$scope", "dialog", function ($scope, dialog) { | |
$scope.close = function(result) { | |
dialog.close(result); | |
}; | |
}]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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