Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 96e5409

Browse files
crisbetojelbourn
authored andcommitted
feat(panel): add interceptors API and onClose hook (#9574)
* Adds an API that can be used for registering interceptors on the panel. * Adds support for an `onClose` interceptor that allows the user to prevent a panel from closing. Fixes #9557.
1 parent b0d9921 commit 96e5409

File tree

2 files changed

+386
-7
lines changed

2 files changed

+386
-7
lines changed

src/components/panel/panel.js

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,47 @@ angular
353353
* @param {!MdPanelPosition} position
354354
*/
355355

356+
/**
357+
* @ngdoc method
358+
* @name MdPanelRef#registerInterceptor
359+
* @description
360+
*
361+
* Registers an interceptor with the panel. The callback should return a promise,
362+
* which will allow the action to continue when it gets resolved, or will
363+
* prevent an action if it is rejected. The interceptors are called sequentially
364+
* and it reverse order. `type` must be one of the following
365+
* values available on `$mdPanel.interceptorTypes`:
366+
* * `CLOSE` - Gets called before the panel begins closing.
367+
*
368+
* @param {string} type Type of interceptor.
369+
* @param {!angular.$q.Promise<any>} callback Callback to be registered.
370+
* @returns {!MdPanelRef}
371+
*/
372+
373+
/**
374+
* @ngdoc method
375+
* @name MdPanelRef#removeInterceptor
376+
* @description
377+
*
378+
* Removes a registered interceptor.
379+
*
380+
* @param {string} type Type of interceptor to be removed.
381+
* @param {function(): !angular.$q.Promise<any>} callback Interceptor to be removed.
382+
* @returns {!MdPanelRef}
383+
*/
384+
385+
/**
386+
* @ngdoc method
387+
* @name MdPanelRef#removeAllInterceptors
388+
* @description
389+
*
390+
* Removes all interceptors. If a type is supplied, only the
391+
* interceptors of that type will be cleared.
392+
*
393+
* @param {string=} type Type of interceptors to be removed.
394+
* @returns {!MdPanelRef}
395+
*/
396+
356397

357398
/*****************************************************************************
358399
* MdPanelPosition *
@@ -743,6 +784,12 @@ function MdPanelService($rootElement, $rootScope, $injector, $window) {
743784
* @type {enum}
744785
*/
745786
this.yPosition = MdPanelPosition.yPosition;
787+
788+
/**
789+
* Possible values for the interceptors that can be registered on a panel.
790+
* @type {enum}
791+
*/
792+
this.interceptorTypes = MdPanelRef.interceptorTypes;
746793
}
747794

748795

@@ -912,9 +959,20 @@ function MdPanelRef(config, $injector) {
912959

913960
/** @private {Function?} */
914961
this._restoreScroll = null;
962+
963+
/**
964+
* Keeps track of all the panel interceptors.
965+
* @private {!Object}
966+
*/
967+
this._interceptors = Object.create(null);
915968
}
916969

917970

971+
MdPanelRef.interceptorTypes = {
972+
CLOSE: 'onClose'
973+
};
974+
975+
918976
/**
919977
* Opens an already created and configured panel. If the panel is already
920978
* visible, does nothing.
@@ -944,13 +1002,15 @@ MdPanelRef.prototype.close = function() {
9441002
var self = this;
9451003

9461004
return this._$q(function(resolve, reject) {
947-
var done = self._done(resolve, self);
948-
var detach = self._simpleBind(self.detach, self);
949-
950-
self.hide()
951-
.then(detach)
952-
.then(done)
953-
.catch(reject);
1005+
self._callInterceptors(MdPanelRef.interceptorTypes.CLOSE).then(function() {
1006+
var done = self._done(resolve, self);
1007+
var detach = self._simpleBind(self.detach, self);
1008+
1009+
self.hide()
1010+
.then(detach)
1011+
.then(done)
1012+
.catch(reject);
1013+
}, reject);
9541014
});
9551015
};
9561016

@@ -1042,6 +1102,7 @@ MdPanelRef.prototype.detach = function() {
10421102
MdPanelRef.prototype.destroy = function() {
10431103
this.config.scope.$destroy();
10441104
this.config.locals = null;
1105+
this._interceptors = null;
10451106
};
10461107

10471108

@@ -1641,6 +1702,106 @@ MdPanelRef.prototype._animateClose = function() {
16411702
});
16421703
};
16431704

1705+
/**
1706+
* Registers a interceptor with the panel. The callback should return a promise,
1707+
* which will allow the action to continue when it gets resolved, or will
1708+
* prevent an action if it is rejected.
1709+
* @param {string} type Type of interceptor.
1710+
* @param {!angular.$q.Promise<!any>} callback Callback to be registered.
1711+
* @returns {!MdPanelRef}
1712+
*/
1713+
MdPanelRef.prototype.registerInterceptor = function(type, callback) {
1714+
var error = null;
1715+
1716+
if (!angular.isString(type)) {
1717+
error = 'Interceptor type must be a string, instead got ' + typeof type;
1718+
} else if (!angular.isFunction(callback)) {
1719+
error = 'Interceptor callback must be a function, instead got ' + typeof callback;
1720+
}
1721+
1722+
if (error) {
1723+
throw new Error('MdPanel: ' + error);
1724+
}
1725+
1726+
var interceptors = this._interceptors[type] = this._interceptors[type] || [];
1727+
1728+
if (interceptors.indexOf(callback) === -1) {
1729+
interceptors.push(callback);
1730+
}
1731+
1732+
return this;
1733+
};
1734+
1735+
/**
1736+
* Removes a registered interceptor.
1737+
* @param {string} type Type of interceptor to be removed.
1738+
* @param {Function} callback Interceptor to be removed.
1739+
* @returns {!MdPanelRef}
1740+
*/
1741+
MdPanelRef.prototype.removeInterceptor = function(type, callback) {
1742+
var index = this._interceptors[type] ?
1743+
this._interceptors[type].indexOf(callback) : -1;
1744+
1745+
if (index > -1) {
1746+
this._interceptors[type].splice(index, 1);
1747+
}
1748+
1749+
return this;
1750+
};
1751+
1752+
1753+
/**
1754+
* Removes all interceptors.
1755+
* @param {string=} type Type of interceptors to be removed.
1756+
* If ommited, all interceptors types will be removed.
1757+
* @returns {!MdPanelRef}
1758+
*/
1759+
MdPanelRef.prototype.removeAllInterceptors = function(type) {
1760+
if (type) {
1761+
this._interceptors[type] = [];
1762+
} else {
1763+
this._interceptors = Object.create(null);
1764+
}
1765+
1766+
return this;
1767+
};
1768+
1769+
1770+
/**
1771+
* Invokes all the interceptors of a certain type sequantially in
1772+
* reverse order. Works in a similar way to `$q.all`, except it
1773+
* respects the order of the functions.
1774+
* @param {string} type Type of interceptors to be invoked.
1775+
* @returns {!angular.$q.Promise<!MdPanelRef>}
1776+
* @private
1777+
*/
1778+
MdPanelRef.prototype._callInterceptors = function(type) {
1779+
var self = this;
1780+
var $q = self._$q;
1781+
var interceptors = self._interceptors && self._interceptors[type] || [];
1782+
1783+
return interceptors.reduceRight(function(promise, interceptor) {
1784+
var isPromiseLike = interceptor && angular.isFunction(interceptor.then);
1785+
var response = isPromiseLike ? interceptor : null;
1786+
1787+
/**
1788+
* For interceptors to reject/cancel subsequent portions of the chain, simply
1789+
* return a `$q.reject(<value>)`
1790+
*/
1791+
return promise.then(function() {
1792+
if (!response) {
1793+
try {
1794+
response = interceptor(self);
1795+
} catch(e) {
1796+
response = $q.reject(e);
1797+
}
1798+
}
1799+
1800+
return response;
1801+
});
1802+
}, $q.resolve(self));
1803+
};
1804+
16441805

16451806
/**
16461807
* Faster, more basic than angular.bind

0 commit comments

Comments
 (0)