-
Notifications
You must be signed in to change notification settings - Fork 3.4k
feat(interimElement): properly handle multiple interims. #9053
Changes from all commits
72e887d
168ba08
dd959a8
f82a7ff
9debca2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -257,15 +257,20 @@ function InterimElementProvider() { | |
* A service used to control inserting and removing an element into the DOM. | ||
* | ||
*/ | ||
var service, stack = []; | ||
|
||
var service; | ||
|
||
var showPromises = []; // Promises for the interim's which are currently opening. | ||
var hidePromises = []; // Promises for the interim's which are currently hiding. | ||
var showingInterims = []; // Interim elements which are currently showing up. | ||
|
||
// Publish instance $$interimElement service; | ||
// ... used as $mdDialog, $mdToast, $mdMenu, and $mdSelect | ||
|
||
return service = { | ||
show: show, | ||
hide: hide, | ||
cancel: cancel, | ||
hide: waitForInterim(hide), | ||
cancel: waitForInterim(cancel), | ||
destroy : destroy, | ||
$injector_: $injector | ||
}; | ||
|
@@ -286,26 +291,35 @@ function InterimElementProvider() { | |
function show(options) { | ||
options = options || {}; | ||
var interimElement = new InterimElement(options || {}); | ||
|
||
// When an interim element is currently showing, we have to cancel it. | ||
// Just hiding it, will resolve the InterimElement's promise, the promise should be | ||
// rejected instead. | ||
var hideExisting = !options.skipHide && stack.length ? service.cancel() : $q.when(true); | ||
|
||
// This hide()s only the current interim element before showing the next, new one | ||
// NOTE: this is not reversible (e.g. interim elements are not stackable) | ||
var hideAction = options.multiple ? $q.resolve() : $q.all(showPromises); | ||
|
||
if (!options.multiple) { | ||
// Wait for all opening interim's to finish their transition. | ||
hideAction = hideAction.then(function() { | ||
// Wait for all closing and showing interim's to be completely closed. | ||
var promiseArray = hidePromises.concat(showingInterims.map(service.cancel)); | ||
return $q.all(promiseArray); | ||
}); | ||
} | ||
|
||
hideExisting.finally(function() { | ||
var showAction = hideAction.then(function() { | ||
|
||
stack.push(interimElement); | ||
interimElement | ||
return interimElement | ||
.show() | ||
.catch(function( reason ) { | ||
//$log.error("InterimElement.show() error: " + reason ); | ||
return reason; | ||
.catch(function(reason) { return reason; }) | ||
.finally(function() { | ||
showPromises.splice(showPromises.indexOf(showAction), 1); | ||
showingInterims.push(interimElement); | ||
}); | ||
|
||
}); | ||
|
||
showPromises.push(showAction); | ||
|
||
// Return a promise that will be resolved when the interim | ||
// element is hidden or cancelled... | ||
|
||
|
@@ -325,27 +339,30 @@ function InterimElementProvider() { | |
* | ||
*/ | ||
function hide(reason, options) { | ||
if ( !stack.length ) return $q.when(reason); | ||
options = options || {}; | ||
|
||
if (options.closeAll) { | ||
var promise = $q.all(stack.reverse().map(closeElement)); | ||
stack = []; | ||
return promise; | ||
// We have to make a shallow copy of the array, because otherwise the map will break. | ||
return $q.all(showingInterims.slice().reverse().map(closeElement)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we reverse it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to show them in reverse, last added interim should hide at first. |
||
} else if (options.closeTo !== undefined) { | ||
return $q.all(stack.splice(options.closeTo).map(closeElement)); | ||
} else { | ||
var interim = stack.pop(); | ||
return closeElement(interim); | ||
return $q.all(showingInterims.slice(options.closeTo).map(closeElement)); | ||
} | ||
|
||
// Hide the latest showing interim element. | ||
return closeElement(showingInterims.pop()); | ||
|
||
function closeElement(interim) { | ||
interim | ||
|
||
var hideAction = interim | ||
.remove(reason, false, options || { }) | ||
.catch(function( reason ) { | ||
//$log.error("InterimElement.hide() error: " + reason ); | ||
return reason; | ||
.catch(function(reason) { return reason; }) | ||
.finally(function() { | ||
hidePromises.splice(hidePromises.indexOf(hideAction), 1); | ||
}); | ||
|
||
showingInterims.splice(showingInterims.indexOf(interim), 1); | ||
hidePromises.push(hideAction); | ||
|
||
return interim.deferred.promise; | ||
} | ||
} | ||
|
@@ -363,46 +380,76 @@ function InterimElementProvider() { | |
* | ||
*/ | ||
function cancel(reason, options) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cancel, hide and show are pretty similar can't we make a more generic function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather not make big changes in the PR, since it's already big enough. Could be an extra PR. |
||
var interim = stack.pop(); | ||
if ( !interim ) return $q.when(reason); | ||
|
||
interim | ||
.remove(reason, true, options || { }) | ||
.catch(function( reason ) { | ||
//$log.error("InterimElement.cancel() error: " + reason ); | ||
return reason; | ||
var interim = showingInterims.pop(); | ||
if (!interim) { | ||
return $q.when(reason); | ||
} | ||
|
||
var cancelAction = interim | ||
.remove(reason, true, options || {}) | ||
.catch(function(reason) { return reason; }) | ||
.finally(function() { | ||
hidePromises.splice(hidePromises.indexOf(cancelAction), 1); | ||
}); | ||
|
||
hidePromises.push(cancelAction); | ||
|
||
// Since Angular 1.6.7, promises will be logged to $exceptionHandler when the promise | ||
// is not handling the rejection. We create a pseudo catch handler, which will prevent the | ||
// promise from being logged to the $exceptionHandler. | ||
return interim.deferred.promise.catch(angular.noop); | ||
} | ||
|
||
/** | ||
* Creates a function to wait for at least one interim element to be available. | ||
* @param callbackFn Function to be used as callback | ||
* @returns {Function} | ||
*/ | ||
function waitForInterim(callbackFn) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we getting a callback function and not resolving a promise? this is weird! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is function which waits for one interim element to be available. Doing it with promises means we need to repeat that logic multiple times (what I had before this part commit) |
||
return function() { | ||
var fnArguments = arguments; | ||
|
||
if (!showingInterims.length) { | ||
// When there are still interim's opening, then wait for the first interim element to | ||
// finish its open animation. | ||
if (showPromises.length) { | ||
return showPromises[0].finally(function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is intentionally. We only want to wait for the first promise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain why you only care about the first promise? This is strange. |
||
return callbackFn.apply(service, fnArguments); | ||
}); | ||
} | ||
|
||
return $q.when("No interim elements currently showing up."); | ||
} | ||
|
||
return callbackFn.apply(service, fnArguments); | ||
}; | ||
} | ||
|
||
/* | ||
* Special method to quick-remove the interim element without animations | ||
* Note: interim elements are in "interim containers" | ||
*/ | ||
function destroy(target) { | ||
var interim = !target ? stack.shift() : null; | ||
var cntr = angular.element(target).length ? angular.element(target)[0].parentNode : null; | ||
|
||
if (cntr) { | ||
// Try to find the interim element in the stack which corresponds to the supplied DOM element. | ||
var filtered = stack.filter(function(entry) { | ||
var currNode = entry.options.element[0]; | ||
return (currNode === cntr); | ||
}); | ||
function destroy(targetEl) { | ||
var interim = !targetEl ? showingInterims.shift() : null; | ||
|
||
// Note: this function might be called when the element already has been removed, in which | ||
// case we won't find any matches. That's ok. | ||
if (filtered.length > 0) { | ||
interim = filtered[0]; | ||
stack.splice(stack.indexOf(interim), 1); | ||
} | ||
var parentEl = angular.element(targetEl).length && angular.element(targetEl)[0].parentNode; | ||
|
||
if (parentEl) { | ||
// Try to find the interim in the stack which corresponds to the supplied DOM element. | ||
var filtered = showingInterims.filter(function(entry) { | ||
return entry.options.element[0] === parentEl; | ||
}); | ||
|
||
// Note: This function might be called when the element already has been removed, | ||
// in which case we won't find any matches. | ||
if (filtered.length) { | ||
interim = filtered[0]; | ||
showingInterims.splice(showingInterims.indexOf(interim), 1); | ||
} | ||
} | ||
|
||
return interim ? interim.remove(SHOW_CANCELLED, false, {'$destroy':true}) : $q.when(SHOW_CANCELLED); | ||
return interim ? interim.remove(SHOW_CANCELLED, false, { '$destroy': true }) : | ||
$q.when(SHOW_CANCELLED); | ||
} | ||
|
||
/* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we muting the errors and resolving the promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really sure about this. I did not write this.
I assume it's because we are resolving / rejecting the promises twice, and this one is just for internal use.
The real promise is returned below.