UNPKG

ionic-angular

Version:

[![Circle CI](https://circleci.com/gh/driftyco/ionic.svg?style=svg)](https://circleci.com/gh/driftyco/ionic)

381 lines (341 loc) 12.6 kB
/** * @ngdoc service * @name $ionicModal * @module ionic * @codepen gblny * @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. * * Put the content of the modal inside of an `<ion-modal-view>` element. * * **Notes:** * - 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. * * - This example assumes your modal is in your main index file or another template file. If it is in its own * template file, remove the script tags and call it by file name. * * @usage * ```html * <script id="my-modal.html" type="text/ng-template"> * <ion-modal-view> * <ion-header-bar> * <h1 class="title">My Modal title</h1> * </ion-header-bar> * <ion-content> * Hello! * </ion-content> * </ion-modal-view> * </script> * ``` * ```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', '$ionicBody', '$compile', '$timeout', '$ionicPlatform', '$ionicTemplateLoader', '$$q', '$log', '$ionicClickBlock', '$window', 'IONIC_BACK_PRIORITY', function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) { /** * @ngdoc controller * @name ionicModal * @module ionic * @description * Instantiated by the {@link ionic.service:$ionicModal} service. * * 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. Will only show the keyboard on iOS, to force the keyboard to show * on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow). * 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(target) { var self = this; if (self.scope.$$destroyed) { $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.'); return $$q.when(); } // on iOS, clicks will sometimes bleed through/ghost click on underlying // elements $ionicClickBlock.show(600); stack.add(self); var modalEl = jqLite(self.modalEl); self.el.classList.remove('hide'); $timeout(function() { if (!self._isShown) return; $ionicBody.addClass(self.viewType + '-open'); }, 400, false); if (!self.el.parentElement) { modalEl.addClass(self.animation); $ionicBody.append(self.el); } // if modal was closed while the keyboard was up, reset scroll view on // next show since we can only resize it once it's visible var scrollCtrl = modalEl.data('$$ionicScrollController'); scrollCtrl && scrollCtrl.resize(); if (target && self.positionView) { self.positionView(target, modalEl); // set up a listener for in case the window size changes self._onWindowResize = function() { if (self._isShown) self.positionView(target, modalEl); }; ionic.on('resize', self._onWindowResize, window); } 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) : noop, IONIC_BACK_PRIORITY.modal ); ionic.views.Modal.prototype.show.call(self); $timeout(function() { if (!self._isShown) return; modalEl.addClass('ng-enter-active'); ionic.trigger('resize'); self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self); self.el.classList.add('active'); self.scope.$broadcast('$ionicHeader.align'); self.scope.$broadcast('$ionicFooter.align'); self.scope.$broadcast('$ionic.modalPresented'); }, 20); return $timeout(function() { if (!self._isShown) return; self.$el.on('touchmove', function(e) { //Don't allow scrolling while open by dragging on backdrop var isInScroll = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'scroll'); if (!isInScroll) { e.preventDefault(); } }); //After animating in, allow hide on backdrop click self.$el.on('click', function(e) { if (self.backdropClickToClose && e.target === self.el && stack.isHighest(self)) { 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); // on iOS, clicks will sometimes bleed through/ghost click on underlying // elements $ionicClickBlock.show(600); stack.remove(self); self.el.classList.remove('active'); modalEl.addClass('ng-leave'); $timeout(function() { if (self._isShown) return; modalEl.addClass('ng-leave-active') .removeClass('ng-enter ng-enter-active active'); self.scope.$broadcast('$ionic.modalRemoved'); }, 20, false); self.$el.off('click'); self._isShown = false; self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self); self._deregisterBackButton && self._deregisterBackButton(); ionic.views.Modal.prototype.hide.call(self); // clean up event listeners if (self.positionView) { ionic.off('resize', self._onWindowResize, window); } return $timeout(function() { $ionicBody.removeClass(self.viewType + '-open'); self.el.classList.add('hide'); }, self.hideDelay || 320); }, /** * @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, deferred, promise; self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self); // Only hide modal, when it is actually shown! // The hide function shows a click-block-div for a split second, because on iOS, // clicks will sometimes bleed through/ghost click on underlying elements. // However, this will make the app unresponsive for short amount of time. // We don't want that, if the modal window is already hidden. if (self._isShown) { promise = self.hide(); } else { deferred = $$q.defer(); deferred.resolve(); promise = deferred.promise; } return promise.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); options.viewType = options.viewType || 'modal'; extend(scope, { $hasHeader: false, $hasSubheader: false, $hasFooter: false, $hasSubfooter: false, $hasTabs: false, $hasTabsTop: false }); // Compile the template var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope); options.$el = element; options.el = element[0]; options.modalEl = options.el.querySelector('.' + options.viewType); var modal = new ModalView(options); modal.scope = scope; // If this wasn't a defined scope, we can assign the viewType to the isolated scope // we created if (!options.scope) { scope[ options.viewType ] = modal; } return modal; }; var modalStack = []; var stack = { add: function(modal) { modalStack.push(modal); }, remove: function(modal) { var index = modalStack.indexOf(modal); if (index > -1 && index < modalStack.length) { modalStack.splice(index, 1); } }, isHighest: function(modal) { var index = modalStack.indexOf(modal); return (index > -1 && index === modalStack.length - 1); } }; 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; }); }, stack: stack }; }]);