/** * @ngdoc service * @name $ionicModal * @module ionic * @description * * Related: {@link ionic.controller:ionicModal ionicModal controller}. * * The Modal is a content pane that can go over the user's main view * temporarily. Usually used for making a choice or editing an item. * Note that you need to put the content of the modal inside a div with the class `modal`. * * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are * called when the modal is removed. * * @usage * ```html * * ``` * ```js * angular.module('testApp', ['ionic']) * .controller('MyController', function($scope, $ionicModal) { * $ionicModal.fromTemplateUrl('my-modal.html', { * scope: $scope, * animation: 'slide-in-up' * }).then(function(modal) { * $scope.modal = modal; * }); * $scope.openModal = function() { * $scope.modal.show(); * }; * $scope.closeModal = function() { * $scope.modal.hide(); * }; * //Cleanup the modal when we're done with it! * $scope.$on('$destroy', function() { * $scope.modal.remove(); * }); * // Execute action on hide modal * $scope.$on('modal.hidden', function() { * // Execute action * }); * // Execute action on remove modal * $scope.$on('modal.removed', function() { * // Execute action * }); * }); * ``` */ IonicModule .factory('$ionicModal', [ '$rootScope', '$document', '$compile', '$timeout', '$ionicPlatform', '$ionicTemplateLoader', '$q', '$log', function($rootScope, $document, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) { /** * @ngdoc controller * @name ionicModal * @module ionic * @description * Instantiated by the {@link ionic.service:$ionicModal} service. * * Hint: Be sure to call [remove()](#remove) when you are done with each modal * to clean it up and avoid memory leaks. * * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are * called when the modal is removed. */ var ModalView = ionic.views.Modal.inherit({ /** * @ngdoc method * @name ionicModal#initialize * @description Creates a new modal controller instance. * @param {object} options An options object with the following properties: * - `{object=}` `scope` The scope to be a child of. * Default: creates a child of $rootScope. * - `{string=}` `animation` The animation to show & hide with. * Default: 'slide-in-up' * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of * the modal when shown. Default: false. * - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop. * Default: true. * - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware * back button on Android and similar devices. Default: true. */ initialize: function(opts) { ionic.views.Modal.prototype.initialize.call(this, opts); this.animation = opts.animation || 'slide-in-up'; }, /** * @ngdoc method * @name ionicModal#show * @description Show this modal instance. * @returns {promise} A promise which is resolved when the modal is finished animating in. */ show: function() { var self = this; if(self.scope.$$destroyed) { $log.error('Cannot call modal.show() after remove(). Please create a new modal instance using $ionicModal.'); return; } var modalEl = jqLite(self.modalEl); self.el.classList.remove('hide'); $timeout(function(){ $document[0].body.classList.add('modal-open'); }, 400); if(!self.el.parentElement) { modalEl.addClass(self.animation); $document[0].body.appendChild(self.el); } modalEl.addClass('ng-enter active') .removeClass('ng-leave ng-leave-active'); self._isShown = true; self._deregisterBackButton = $ionicPlatform.registerBackButtonAction( self.hardwareBackButtonClose ? angular.bind(self, self.hide) : angular.noop, PLATFORM_BACK_BUTTON_PRIORITY_MODAL ); self._isOpenPromise = $q.defer(); ionic.views.Modal.prototype.show.call(self); $timeout(function(){ modalEl.addClass('ng-enter-active'); self.scope.$parent && self.scope.$parent.$broadcast('modal.shown', self); self.el.classList.add('active'); }, 20); return $timeout(function() { //After animating in, allow hide on backdrop click self.$el.on('click', function(e) { if (self.backdropClickToClose && e.target === self.el) { self.hide(); } }); }, 400); }, /** * @ngdoc method * @name ionicModal#hide * @description Hide this modal instance. * @returns {promise} A promise which is resolved when the modal is finished animating out. */ hide: function() { var self = this; var modalEl = jqLite(self.modalEl); self.el.classList.remove('active'); modalEl.addClass('ng-leave'); $timeout(function(){ modalEl.addClass('ng-leave-active') .removeClass('ng-enter ng-enter-active active'); }, 20); self.$el.off('click'); self._isShown = false; self.scope.$parent && self.scope.$parent.$broadcast('modal.hidden', self); self._deregisterBackButton && self._deregisterBackButton(); ionic.views.Modal.prototype.hide.call(self); return $timeout(function(){ $document[0].body.classList.remove('modal-open'); self.el.classList.add('hide'); }, 500); }, /** * @ngdoc method * @name ionicModal#remove * @description Remove this modal instance from the DOM and clean up. * @returns {promise} A promise which is resolved when the modal is finished animating out. */ remove: function() { var self = this; self.scope.$parent && self.scope.$parent.$broadcast('modal.removed', self); return self.hide().then(function() { self.scope.$destroy(); self.$el.remove(); }); }, /** * @ngdoc method * @name ionicModal#isShown * @returns boolean Whether this modal is currently shown. */ isShown: function() { return !!this._isShown; } }); var createModal = function(templateString, options) { // Create a new scope for the modal var scope = options.scope && options.scope.$new() || $rootScope.$new(true); extend(scope, { $hasHeader: false, $hasSubheader: false, $hasFooter: false, $hasSubfooter: false, $hasTabs: false, $hasTabsTop: false }); // Compile the template var element = $compile('' + templateString + '')(scope); options.$el = element; options.el = element[0]; options.modalEl = options.el.querySelector('.modal'); var modal = new ModalView(options); modal.scope = scope; // If this wasn't a defined scope, we can assign 'modal' to the isolated scope // we created if(!options.scope) { scope.modal = modal; } return modal; }; return { /** * @ngdoc method * @name $ionicModal#fromTemplate * @param {string} templateString The template string to use as the modal's * content. * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method. * @returns {object} An instance of an {@link ionic.controller:ionicModal} * controller. */ fromTemplate: function(templateString, options) { var modal = createModal(templateString, options || {}); return modal; }, /** * @ngdoc method * @name $ionicModal#fromTemplateUrl * @param {string} templateUrl The url to load the template from. * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method. * options object. * @returns {promise} A promise that will be resolved with an instance of * an {@link ionic.controller:ionicModal} controller. */ fromTemplateUrl: function(url, options, _) { var cb; //Deprecated: allow a callback as second parameter. Now we return a promise. if (angular.isFunction(options)) { cb = options; options = _; } return $ionicTemplateLoader.load(url).then(function(templateString) { var modal = createModal(templateString, options || {}); cb && cb(modal); return modal; }); } }; }]);