<!DOCTYPE html>
<html>

	<head>
		<script data-require="angular.js@*" data-semver="1.2.0-rc3-nonmin" src="http://code.angularjs.org/1.2.0-rc.3/angular.js"></script>
		<script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
		<link data-require="jqueryui@*" data-semver="1.10.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
		<script data-require="jqueryui@*" data-semver="1.10.0" src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>

		<script src="dialog-service.js"></script>
		<script src="app.js"></script>
	</head>

	<body ng-app="dialogApp">


		<!--
			The button that displays the dialog when pressed
		-->
		<div ng-controller="buttonCtrl">
			<button ng-click="openClick()">Test</button>
		</div>



		<!--
			This is the template for the main dialog that is displayed. It uses the
			dialogCtrl controller in app.js.
		-->
		<script type="text/ng-template" id="dialogTemplate.html">
			<div ng-controller="dialogCtrl">

				First Name<br>
				<input type="text" ng-model="model.firstName" /><br>
				Last Name<br>
				<input type="text" ng-model="model.lastName" /><br>

				<button ng-click="cancelClick()">Cancel</button>
				<button ng-click="saveClick()">Save</button>
				<button ng-click="confirmClick()">Confirm</button>
			</div>
		</script>



		<!--
			This is the template for the confirmation popup. It uses the confirmCtrl
			controller in app.js.
		-->
		<script type="text/ng-template" id="confirmTemplate.html">
			<div ng-controller="confirmCtrl">
				Are you sure?
				<button ng-click="confirmClick()">Yes</button>
				<button ng-click="cancelClick()">No</button>
			</div>
		</script>



	</body>

</html>
angular-jquery-dialog-service
=============================

# Overview
This service allows you to easily work with jQuery UI dialogs from Angular.js. A working sample can be viewed on [Plunker][2].

# Methods
The service exposes three methods for controlling the dialogs. These methods are `open()`, `close()`, and `cancel()`.

## open(id, template, model, options)
The open method displays a dialog. The `id` argument is a unique name to identify this dialog when calling other methods on the service such as close and cancel.


The `template` argument specifies the id of the script block that contains the template to use for the dialog. Here is an example template:

```
<script type="text/ng-template" id="dialogTemplate.html">

	<!-- Controller for Dialog -->
	<div ng-controller="dialogCtrl">

		<!-- The form -->
		First Name<br>
		<input type="text" ng-model="model.firstName" /><br>
		Last Name<br>
		<input type="text" ng-model="model.lastName" /><br>

		<!-- The buttons -->
		<button ng-click="cancelClick()">Cancel</button>
		<button ng-click="saveClick()">Save</button>
		<button ng-click="confirmClick()">Confirm</button>
	</div>
</script>
```

In the case above, `template` would be set to "dialogTemplate.html".

The `model` argument contains the data that should be passed to the dialog controller's scope. It is actually injected into the dialog controller's parent scope, but it is available as `$scope.model` within the dialog.

Finally, the `options` argument contains all of the [jQuery UI dialog options][1] that you would normally pass in the call to `dialog(options)`.

The open method returns a promise that is resolved when the user closes the dialog. If the dialog controller calls dialogService.close(model), the resolve function will be called. If `cancel()` is called or the user closed the dialog using the X or ESC, the reject function will be called.

Here is an example of an open call that opens a dialog whose template is in a script block assigned an id of "dialogTemplate.html":

```javascript
dialogService.open("myDialog","dialogTemplate.html",
		model: {
			firstName: "Jason",
			lastName: "Stadler",
			update: false
		},
		options: {
			autoOpen: false,
			modal: true
		}
	}).then(
			function(result) {
				console.log("Closed");
				console.log(result);
			},
			function(error) {
				console.log("Cancelled");
			}
	);
```

## close(id, model)

This method is typically called by the dialog controller to close the dialog. The `id` argument is the same string passed to the open method. The `model` is the data the dialog should pass back in the promise to the caller.

## cancel(id)

This method is typically called by the dialog controller to cancel the dialog. The `id` argument is the same string passed to the open method.


[1]: http://api.jquery.ui/dialog  "JQuery UI Dialog Documentation"
[2]: http://plnkr.co/edit/ADYEsplnYr8NHqASCDgS  "Plunker sample"

var app = angular.module('dialogApp', ['dialogService']);

app.controller('buttonCtrl', ['$scope', 'dialogService',
	function($scope, dialogService) {
		$scope.openClick = function() {

			// The data for the dialog
			var model = {
					firstName: "Jason",
					lastName: "Stadler"
				};

			// jQuery UI dialog options
			var options = {
					autoOpen: false,
					modal: true,
					close: function(event, ui) {
						console.log("Predefined close");
					}
				};

			// Open the dialog
			dialogService.open("myDialog","dialogTemplate.html", model, options)
			.then(
				function(result) {
					console.log("Close");
					console.log(result);
				},
				function(error) {
					console.log("Cancelled");
				}
			);

		};
	}
]);

app.controller('dialogCtrl', ['$scope', 'dialogService',
	function($scope, dialogService) {

		// $scope.model contains the object passed to open in config.model

		$scope.saveClick = function() {
			dialogService.close("myDialog", $scope.model);
		};

		$scope.cancelClick = function() {
			dialogService.cancel("myDialog");
		};

		$scope.confirmClick = function() {
			// Open another dialog here
			dialogService.open("myConfirm", "confirmTemplate.html")
			.then(
				function(result) {
					console.log("Confirm");
				},
				function(error) {
					console.log("Cancel");
				}
			);
		};

	}
]);

app.controller('confirmCtrl', ['$scope', 'dialogService',
	function($scope, dialogService) {

		$scope.confirmClick = function() {
			dialogService.close("myConfirm");
		};

		$scope.cancelClick = function() {
			dialogService.cancel("myConfirm");
		};

	}
]);
angular.module('dialogService', []).service('dialogService',
	['$rootScope', '$q', '$compile', '$templateCache',
	function($rootScope, $q, $compile, $templateCache) {

			_this = this;
			this.dialogs = {};

			this.open = function(id, template, model, options) {

				// Check our required arguments
				if (!angular.isDefined(id)) {
					throw "dialogService requires id in call to open";
				}

				if (!angular.isDefined(template)) {
					throw "dialogService requires template in call to open";
				}

				// Set the defaults for model
				if (!angular.isDefined(model)) {
					model = null;
				}

				// Copy options so the change ot close isn't propogated back.
				// Extend is used instead of copy because window references are
				// often used in the options for positioning and they can't be deep
				// copied.
				var dialogOptions = {};
				if (angular.isDefined(options)) {
					angular.extend(dialogOptions, options);
				}

				// Initialize our dialog structure
				var dialog = { scope: null, ref: null, deferred: null };

				// Get the template and trim to make it valid
				var dialogTemplate = $templateCache.get(template);
				if (!angular.isDefined(dialogTemplate)) {
					throw "dialogService could not find template " + template;
				}
				dialogTemplate = dialogTemplate.trim();

				// Create a new scope, inherited from the parent.
				dialog.scope = $rootScope.$new();
				dialog.scope.model = model;
				var dialogLinker = $compile(dialogTemplate);
				dialog.ref = $(dialogLinker(dialog.scope));

				// Hande the case where the user provides a custom close and also
				// the case where the user clicks the X or ESC and doesn't call
				// close or cancel.
				var customCloseFn = dialogOptions.close;
				var cleanupFn = this.cleanup;
				dialogOptions.close = function(event, ui) {
					if (customCloseFn) {
						customCloseFn(event, ui);
					}
					cleanupFn(id);
				};

				// Initialize the dialog and open it
				dialog.ref.dialog(dialogOptions);
				dialog.ref.dialog("open");

				// Cache the dialog
				_this.dialogs[id] = dialog;

				// Create our promise, cache it to complete later, and return it
				dialog.deferred = $q.defer();
				return dialog.deferred.promise;
			};

			this.close = function(id, result) {
				// Get the dialog and throw exception if not found
				var dialog = _this.getExistingDialog(id);

				// Notify those waiting for the result
				// This occurs first because the close calls the close handler on the
				// dialog whose default action is to cancel.
				dialog.deferred.resolve(result);

				// Close the dialog (must be last)
				dialog.ref.dialog("close");
			};

			this.cancel = function(id) {
				// Get the dialog and throw exception if not found
				var dialog = _this.getExistingDialog(id);

				// Notify those waiting for the result
				// This occurs first because the cancel calls the close handler on the
				// dialog whose default action is to cancel.
				dialog.deferred.reject();

				// Cancel and close the dialog (must be last)
				dialog.ref.dialog("close");
			};

			/* private */
			this.cleanup = function(id) {
				// Get the dialog and throw exception if not found
				var dialog = _this.getExistingDialog(id);

				// This is only called from the close handler of the dialog
				// in case the x or escape are used to cancel the dialog. Don't
				// call this from close, cancel, or externally.
				dialog.deferred.reject();
				dialog.scope.$destroy();

				// Remove the object from the DOM
				dialog.ref.remove();

				// Delete the dialog from the cache
				delete _this.dialogs[id];
			};

			/* private */
			this.getExistingDialog = function(id) {
				// Get the dialog from the cache
				var dialog = _this.dialogs[id];
				// Throw an exception if the dialog is not found
				if (!angular.isDefined(dialog)) {
					throw "DialogService does not have a reference to dialog id " + id;
				}
				return dialog;
			};

		}
]);