');
diff --git a/src/components/bottomSheet/demoBasicUsage/script.js b/src/components/bottomSheet/demoBasicUsage/script.js
index d49fe28d702..b8fdde4291b 100644
--- a/src/components/bottomSheet/demoBasicUsage/script.js
+++ b/src/components/bottomSheet/demoBasicUsage/script.js
@@ -22,7 +22,7 @@ angular.module('bottomSheetDemo1', ['ngMaterial'])
controller: 'ListBottomSheetCtrl',
targetEvent: $event
}).then(function(clickedItem) {
- $scope.alert = clickedItem.name + ' clicked!';
+ $scope.alert = clickedItem['name'] + ' clicked!';
});
};
@@ -35,7 +35,7 @@ angular.module('bottomSheetDemo1', ['ngMaterial'])
}).then(function(clickedItem) {
$mdToast.show(
$mdToast.simple()
- .content(clickedItem.name + ' clicked!')
+ .content(clickedItem['name'] + ' clicked!')
.position('top right')
.hideDelay(1500)
);
diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js
index 3090ed51889..541c3c0f4b7 100644
--- a/src/components/dialog/dialog.js
+++ b/src/components/dialog/dialog.js
@@ -10,7 +10,7 @@ angular
.directive('mdDialog', MdDialogDirective)
.provider('$mdDialog', MdDialogProvider);
-function MdDialogDirective($$rAF, $mdTheming) {
+function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
return {
restrict: 'E',
link: function(scope, element, attr) {
@@ -25,9 +25,19 @@ function MdDialogDirective($$rAF, $mdTheming) {
//-- delayed image loading may impact scroll height, check after images are loaded
angular.element(images).on('load', addOverflowClass);
}
+
+ scope.$on('$destroy', function() {
+ $mdDialog.destroy();
+ });
+
+ /**
+ *
+ */
function addOverflowClass() {
element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight);
}
+
+
});
}
};
@@ -530,16 +540,30 @@ function MdDialogProvider($$interimElementProvider) {
function onRemove(scope, element, options) {
options.deactivateListeners();
options.unlockScreenReader();
+ options.hideBackdrop(options.$destroy);
- options.hideBackdrop();
+ // For navigation $destroy events, do a quick, non-animated removal,
+ // but for normal closes (from clicks, etc) animate the removal
- return dialogPopOut(element, options)
- .finally(function() {
- angular.element($document[0].body).removeClass('md-dialog-is-showing');
- element.remove();
+ return !!options.$destroy ? detachAndClean() : animateRemoval().then( detachAndClean );
- options.origin.focus();
- });
+ /**
+ * For normal closes, animate the removal.
+ * For forced closes (like $destroy events), skip the animations
+ */
+ function animateRemoval() {
+ return dialogPopOut(element, options);
+ }
+
+ /**
+ * Detach the element
+ */
+ function detachAndClean() {
+ angular.element($document[0].body).removeClass('md-dialog-is-showing');
+ element.remove();
+
+ if (!options.$destroy) options.origin.focus();
+ }
}
/**
@@ -661,10 +685,12 @@ function MdDialogProvider($$interimElementProvider) {
/**
* Hide modal backdrop element...
*/
- options.hideBackdrop = function hideBackdrop() {
+ options.hideBackdrop = function hideBackdrop($destroy) {
if (options.backdrop) {
- $animate.leave(options.backdrop);
+ if ( !!$destroy ) options.backdrop.remove();
+ else $animate.leave(options.backdrop);
}
+
if (options.disableParentScroll) {
options.restoreScroll();
delete options.restoreScroll;
@@ -755,7 +781,6 @@ function MdDialogProvider($$interimElementProvider) {
var isFixed = $window.getComputedStyle($document[0].body).position == 'fixed';
var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null;
-
var height = backdrop ? Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10)))) : 0;
container.css({
diff --git a/src/components/dialog/dialog.spec.js b/src/components/dialog/dialog.spec.js
index ced5a33c829..bd4ab3e8994 100644
--- a/src/components/dialog/dialog.spec.js
+++ b/src/components/dialog/dialog.spec.js
@@ -158,6 +158,28 @@ describe('$mdDialog', function() {
expect(container.length).toBe(0);
}));
+ it('should remove `md-dialog-container` on scope.$destroy()', inject(function($mdDialog, $rootScope, $timeout) {
+ var container, parent = angular.element('
');
+
+ $mdDialog.show(
+ $mdDialog.alert({
+ template: '' +
+ '
' +
+ ' ' +
+ ' Muppets are the best
' +
+ ' ' +
+ '',
+ parent: parent
+ })
+ );
+
+ runAnimation(parent.find('md-dialog'));
+ $rootScope.$destroy();
+ container = angular.element(parent[0].querySelector('.md-dialog-container'));
+
+ expect(container.length).toBe(0);
+ }));
+
});
describe('#confirm()', function() {
diff --git a/src/components/menu/js/menuController.js b/src/components/menu/js/menuController.js
index 7ccfc6f88cb..9c418604618 100644
--- a/src/components/menu/js/menuController.js
+++ b/src/components/menu/js/menuController.js
@@ -100,7 +100,7 @@ function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout) {
preserveElement: self.isInMenuBar || self.nestedMenus.length > 0,
parent: self.isInMenuBar ? $element : 'body'
});
- }
+ };
// Expose a open function to the child scope for html to use
$scope.$mdOpenMenu = this.open;
@@ -133,6 +133,10 @@ function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout) {
this.containerProxy && this.containerProxy(ev);
};
+ this.destroy = function() {
+ return $mdMenu.destroy();
+ };
+
// Use the $mdMenu interim element service to close the menu contents
this.close = function closeMenu(skipFocus, closeOpts) {
if ( !self.isOpen ) return;
@@ -140,12 +144,13 @@ function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout) {
$scope.$emit('$mdMenuClose', $element);
$mdMenu.hide(null, closeOpts);
+
if (!skipFocus) {
var el = self.restoreFocusTo || $element.find('button')[0];
if (el instanceof angular.element) el = el[0];
el.focus();
}
- }
+ };
/**
* Build a nice object out of our string attribute which specifies the
diff --git a/src/components/menu/js/menuDirective.js b/src/components/menu/js/menuDirective.js
index 0b027658bbf..da60e0180c9 100644
--- a/src/components/menu/js/menuDirective.js
+++ b/src/components/menu/js/menuDirective.js
@@ -191,8 +191,11 @@ function MenuDirective($mdUtil) {
mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar });
scope.$on('$destroy', function() {
- menuContainer.remove();
- mdMenuCtrl.close();
+ mdMenuCtrl
+ .destroy()
+ .finally(function(){
+ menuContainer.remove();
+ });
});
}
diff --git a/src/components/menu/js/menuServiceProvider.js b/src/components/menu/js/menuServiceProvider.js
index 982ff9c5ba1..7832af83c68 100644
--- a/src/components/menu/js/menuServiceProvider.js
+++ b/src/components/menu/js/menuServiceProvider.js
@@ -22,7 +22,7 @@ function MenuProvider($$interimElementProvider) {
});
/* @ngInject */
- function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF, $animateCss, $animate, $timeout) {
+ function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF, $animateCss, $animate) {
var animator = $mdUtil.dom.animator;
return {
@@ -63,14 +63,8 @@ function MenuProvider($$interimElementProvider) {
* Hide and destroys the backdrop created by showBackdrop()
*/
return function hideBackdrop() {
- if (options.backdrop) {
- // Override duration to immediately remove invisible backdrop
- options.backdrop.off('click');
- $animate.leave(options.backdrop, {duration:0});
- }
- if (options.disableParentScroll) {
- options.restoreScroll();
- }
+ if (options.backdrop) options.backdrop.remove();
+ if (options.disableParentScroll) options.restoreScroll();
};
}
@@ -83,14 +77,28 @@ function MenuProvider($$interimElementProvider) {
opts.cleanupResizing();
opts.hideBackdrop();
- return $animateCss(element, {addClass: 'md-leave'})
- .start()
- .then(function() {
- element.removeClass('md-active');
+ // For navigation $destroy events, do a quick, non-animated removal,
+ // but for normal closes (from clicks, etc) animate the removal
+
+ return (opts.$destroy === true) ? detachAndClean() : animateRemoval().then( detachAndClean );
+
+ /**
+ * For normal closes, animate the removal.
+ * For forced closes (like $destroy events), skip the animations
+ */
+ function animateRemoval() {
+ return $animateCss(element, {addClass: 'md-leave'}).start();
+ }
+
+ /**
+ * Detach the element
+ */
+ function detachAndClean() {
+ element.removeClass('md-active');
+ detachElement(element, opts);
+ opts.alreadyOpen = false;
+ }
- detachElement(element, opts);
- opts.alreadyOpen = false;
- });
}
/**
@@ -361,7 +369,7 @@ function MenuProvider($$interimElementProvider) {
}
/**
- * Use browser to remove this element without triggering a $destory event
+ * Use browser to remove this element without triggering a $destroy event
*/
function detachElement(element, opts) {
if (!opts.preserveElement) {
diff --git a/src/components/menu/menu.scss b/src/components/menu/menu.scss
index 301b817f63a..940405e1d5d 100644
--- a/src/components/menu/menu.scss
+++ b/src/components/menu/menu.scss
@@ -17,7 +17,6 @@ $max-dense-menu-height: 2 * $baseline-grid + $max-visible-items * $dense-menu-it
margin-top: $baseline-grid / 2;
margin-bottom: $baseline-grid / 2;
height: 1px;
- min-height: 1px;
width: 100%;
}
diff --git a/src/components/menu/menu.spec.js b/src/components/menu/menu.spec.js
index a6b8fb8c4aa..6c649a7ce55 100644
--- a/src/components/menu/menu.spec.js
+++ b/src/components/menu/menu.spec.js
@@ -86,6 +86,7 @@ describe('material.components.menu', function() {
});
it('closes on backdrop click', inject(function($document) {
+
openMenu(setup());
expect(getOpenMenuContainer().length).toBe(1);
@@ -96,6 +97,7 @@ describe('material.components.menu', function() {
expect(getOpenMenuContainer().length).toBe(0);
}));
+
it('closes on escape', inject(function($document, $mdConstant) {
openMenu(setup());
expect(getOpenMenuContainer().length).toBe(1);
@@ -108,6 +110,16 @@ describe('material.components.menu', function() {
expect(getOpenMenuContainer().length).toBe(0);
}));
+ it('closes on $destroy', inject(function($document, $rootScope) {
+ var scope = $rootScope.$new();
+ openMenu( setup(null,false,scope) );
+
+ expect(getOpenMenuContainer().length).toBe(1);
+ scope.$destroy();
+
+ expect(getOpenMenuContainer().length).toBe(0);
+ }));
+
describe('closes with -', function() {
it('closes on normal option click', function() {
expect(getOpenMenuContainer().length).toBe(0);
@@ -164,7 +176,7 @@ describe('material.components.menu', function() {
}
});
- function setup(triggerType, noEvent) {
+ function setup(triggerType, noEvent, scope) {
var menu,
template = $mdUtil.supplant('' +
'
' +
@@ -180,7 +192,7 @@ describe('material.components.menu', function() {
$rootScope.doSomething = function($event) {
menuActionPerformed = true;
};
- menu = $compile(template)($rootScope);
+ menu = $compile(template)(scope || $rootScope);
});
attachedElements.push(menu);
@@ -193,7 +205,6 @@ describe('material.components.menu', function() {
// Internal methods
// ********************************************
-
function getOpenMenuContainer() {
var res;
inject(function($document) {
diff --git a/src/components/select/select.js b/src/components/select/select.js
index 225ce216aee..517c615250c 100755
--- a/src/components/select/select.js
+++ b/src/components/select/select.js
@@ -144,7 +144,6 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
attr.tabindex = attr.tabindex || '0';
return function postLink(scope, element, attr, ctrls) {
- var isOpen;
var isDisabled;
var containerCtrl = ctrls[0];
@@ -316,20 +315,23 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
element.attr(ariaAttrs);
scope.$on('$destroy', function() {
- if (isOpen) {
- $mdSelect.hide().finally(function() {
- selectContainer.remove();
+ $mdSelect
+ .destroy()
+ .finally(function() {
+ if ( selectContainer ) {
+ selectContainer.remove();
+ }
+
+ if (containerCtrl) {
+ containerCtrl.setFocused(false);
+ containerCtrl.setHasValue(false);
+ containerCtrl.input = null;
+ }
});
- } else {
- selectContainer.remove();
- }
- if (containerCtrl) {
- containerCtrl.setFocused(false);
- containerCtrl.setHasValue(false);
- containerCtrl.input = null;
- }
});
+
+
function inputCheckValue() {
// The select counts as having a value if one or more options are selected,
// or if the input's validity state says it has bad input (eg string in a number input)
@@ -345,7 +347,8 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
selectScope = scope.$new();
$mdTheming.inherit(selectContainer, element);
if (element.attr('md-container-class')) {
- selectContainer[0].setAttribute('class', selectContainer[0].getAttribute('class') + ' ' + element.attr('md-container-class'));
+ var value = selectContainer[0].getAttribute('class') + ' ' + element.attr('md-container-class');
+ selectContainer[0].setAttribute('class', value);
}
selectContainer = $compile(selectContainer)(selectScope);
selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu');
@@ -374,7 +377,7 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
}
function openSelect() {
- scope.$apply('isOpen = true');
+ selectScope.isOpen = true;
$mdSelect.show({
scope: selectScope,
@@ -385,7 +388,7 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
hasBackdrop: true,
loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false
}).then(function() {
- isOpen = false;
+ selectScope.isOpen = false;
});
}
};
@@ -782,38 +785,42 @@ function SelectProvider($$interimElementProvider) {
* Interim-element onRemove logic....
*/
function onRemove(scope, element, opts) {
+ opts = opts || { };
opts.cleanupInteraction();
opts.cleanupResizing();
opts.hideBackdrop();
- return $animateCss(element, {addClass: 'md-leave'})
- .start()
- .then(function(response) {
-
- configureAria(opts.target, false);
- element.removeClass('md-active');
-
- announceClosed(opts);
- detachElement(element, opts);
-
- return response;
- })
- .finally(function() {
- opts.restoreFocus && opts.target.focus();
- });
-
- // If we want to ignore leave animations (and remove immediately):
- //
- // configureAria(opts.target, false);
- //
- // element.addClass('md-leave');
- // element.removeClass('md-active');
- //
- // announceClosed(opts);
- // detachElement(element, opts);
- // opts.restoreFocus && opts.target.focus();
- //
- // return $q.when(true);
+ // For navigation $destroy events, do a quick, non-animated removal,
+ // but for normal closes (from clicks, etc) animate the removal
+
+ return (opts.$destroy === true) ? detachAndClean() : animateRemoval().then( detachAndClean );
+
+ /**
+ * For normal closes (eg clicks), animate the removal.
+ * For forced closes (like $destroy events from navigation),
+ * skip the animations
+ */
+ function animateRemoval() {
+ return $animateCss(element, {addClass: 'md-leave'}).start();
+ }
+
+ /**
+ * Detach the element and cleanup prior changes
+ */
+ function detachAndClean() {
+ configureAria(opts.target, false);
+
+ element.attr('opacity', 0);
+ element.removeClass('md-active');
+ detachElement(element, opts);
+
+ announceClosed(opts);
+
+ if (!opts.$destroy && opts.restoreFocus) {
+ opts.target.focus();
+ }
+ }
+
}
/**
@@ -911,14 +918,10 @@ function SelectProvider($$interimElementProvider) {
* Hide modal backdrop element...
*/
return function hideBackdrop() {
- if (options.backdrop) {
- // Override duration to immediately remove invisible backdrop
- $animate.leave(options.backdrop, {duration: 0});
- }
- if (options.disableParentScroll) {
- options.restoreScroll();
- delete options.restoreScroll;
- }
+ if (options.backdrop) options.backdrop.remove();
+ if (options.disableParentScroll) options.restoreScroll();
+
+ delete options.restoreScroll;
}
}
@@ -1164,7 +1167,7 @@ function SelectProvider($$interimElementProvider) {
}
/**
- * Use browser to remove this element without triggering a $destory event
+ * Use browser to remove this element without triggering a $destroy event
*/
function detachElement(element, opts) {
if (element[0].parentNode === opts.parent[0]) {
diff --git a/src/components/select/select.spec.js b/src/components/select/select.spec.js
index 44f6d5ea890..0e5117f28c9 100755
--- a/src/components/select/select.spec.js
+++ b/src/components/select/select.spec.js
@@ -1,116 +1,18 @@
describe('', function() {
var attachedElements = [];
- afterEach(function() {
- attachedElements.forEach(function(element) {
- element.remove();
- });
- attachedElements = [];
- });
-
beforeEach(module('material.components.input'));
beforeEach(module('material.components.select'));
-
- function setupSelect(attrs, options, bNoLabel) {
- var el;
-
- inject(function($compile, $rootScope) {
- var src = '';
- if (!bNoLabel) {
- src += '';
- }
- src += '' + optTemplate(options) + '';
- var template = angular.element(src);
- el = $compile(template)($rootScope);
- $rootScope.$digest();
- });
- attachedElements.push(el);
-
- return el;
- }
-
- function setup(attrs, options) {
- var el;
- inject(function($compile, $rootScope) {
- var optionsTpl = optTemplate(options);
- var fullTpl = '' + optionsTpl +
- '';
- el = $compile(fullTpl)($rootScope);
- $rootScope.$apply();
- });
- attachedElements.push(el);
-
- return el;
- }
-
- function setupMultiple(attrs, options) {
- attrs = (attrs || '') + ' multiple';
- return setup(attrs, options);
- }
-
- function optTemplate(options) {
- var optionsTpl = '';
- inject(function($rootScope) {
- if (angular.isArray(options)) {
- $rootScope.$$values = options;
- optionsTpl = '{{value}}';
- } else if (angular.isString(options)) {
- optionsTpl = options;
- }
+ afterEach(inject(function($document) {
+ attachedElements.forEach(function(element) {
+ element.remove();
});
- return optionsTpl;
- }
-
- function selectedOptions(el) {
- return angular.element(el[0].querySelectorAll('md-option[selected]'));
- }
-
- function openSelect(el) {
- try {
- el.triggerHandler('click');
- waitForSelectOpen();
- } catch(e) { }
- }
-
-
- function pressKey(el, code) {
- el.triggerHandler({
- type: 'keydown',
- keyCode: code
- });
- }
-
- function waitForSelectOpen() {
- try {
- inject(function($rootScope, $timeout, $$rAF) {
- $rootScope.$digest();
-
- $$rAF.flush(); // flush $animate.enter(backdrop)
- $timeout.flush(); // flush response
- $$rAF.flush(); // flush $animateCss
- $timeout.flush(); // flush response
-
- $rootScope.$digest();
- });
- } catch(e) { }
- }
-
- function waitForSelectClose() {
- try {
- inject(function($rootScope, $timeout, $$rAF) {
- $rootScope.$digest();
-
- $$rAF.flush(); // flush $animate.leave(backdrop)
- $timeout.flush(); // flush response
-
- $rootScope.$digest();
+ attachedElements = [];
- $$rAF.flush();
- $rootScope.$digest();
- });
- } catch(e) { }
- }
+ var selectMenus = $document.find('md-select-menu');
+ selectMenus.remove();
+ }));
it('should preserve tabindex', inject(function($document) {
var select = setupSelect('tabindex="2", ng-model="val"').find('md-select');
@@ -138,7 +40,7 @@ describe('', function() {
expect(container.classList.contains('test')).toBe(true);
}));
- it('closes the menu if the element is destroyed', inject(function($document, $rootScope) {
+ it('closes the menu if the element on backdrop click', inject(function($document, $rootScope) {
var called = false;
$rootScope.onClose = function() {
called = true;
@@ -156,6 +58,24 @@ describe('', function() {
expect(called).toBe(true);
}));
+ it('closes the menu during scope.$destroy()', inject(function($document, $rootScope, $timeout) {
+ var container = angular.element("");
+ var scope = $rootScope.$new();
+ var select = setupSelect(' ng-model="val" ', [1, 2, 3], false, scope).find('md-select');
+
+ $document[0].body.appendChild(container[0]);
+ container.append(select);
+
+ openSelect(select);
+
+ scope.$destroy();
+ $rootScope.$digest();
+ $timeout.flush();
+
+ expect($document[0].querySelector("md-select-menu")).toBe(null);
+ }));
+
+
it('restores focus to select when the menu is closed', inject(function($document) {
var select = setupSelect('ng-model="val"').find('md-select');
openSelect(select);
@@ -168,8 +88,6 @@ describe('', function() {
// FIXME- does not work with minified, jquery
//expect($document[0].activeElement).toBe(select[0]);
-
- select.remove();
}));
it('should not convert numbers to strings', inject(function($compile, $rootScope) {
@@ -182,11 +100,6 @@ describe('', function() {
}));
describe('input container', function() {
- beforeEach(inject(function($document) {
- var selectMenus = $document.find('md-select-menu');
- selectMenus.remove();
- }));
-
it('should set has-value class on container for non-ng-model input', inject(function($rootScope, $document) {
var el = setupSelect('ng-model="$root.model"', [1, 2, 3]);
var select = el.find('md-select');
@@ -831,4 +744,105 @@ describe('', function() {
}));
});
});
+
+ function setupSelect(attrs, options, skipLabel, scope) {
+ var el;
+
+ inject(function($compile, $rootScope) {
+ var src = '';
+ if (!skipLabel) {
+ src += '';
+ }
+ src += '' + optTemplate(options) + '';
+ var template = angular.element(src);
+ el = $compile(template)(scope || $rootScope);
+ $rootScope.$digest();
+ });
+ attachedElements.push(el);
+
+ return el;
+ }
+
+ function setup(attrs, options) {
+ var el;
+ inject(function($compile, $rootScope) {
+ var optionsTpl = optTemplate(options);
+ var fullTpl = '' + optionsTpl +
+ '';
+ el = $compile(fullTpl)($rootScope);
+ $rootScope.$apply();
+ });
+ attachedElements.push(el);
+
+ return el;
+ }
+
+ function setupMultiple(attrs, options) {
+ attrs = (attrs || '') + ' multiple';
+ return setup(attrs, options);
+ }
+
+ function optTemplate(options) {
+ var optionsTpl = '';
+ inject(function($rootScope) {
+ if (angular.isArray(options)) {
+ $rootScope.$$values = options;
+ optionsTpl = '{{value}}';
+ } else if (angular.isString(options)) {
+ optionsTpl = options;
+ }
+ });
+ return optionsTpl;
+ }
+
+ function selectedOptions(el) {
+ return angular.element(el[0].querySelectorAll('md-option[selected]'));
+ }
+
+ function openSelect(el) {
+ try {
+ el.triggerHandler('click');
+ waitForSelectOpen();
+ } catch(e) { }
+ }
+
+
+ function pressKey(el, code) {
+ el.triggerHandler({
+ type: 'keydown',
+ keyCode: code
+ });
+ }
+
+ function waitForSelectOpen() {
+ try {
+ inject(function($rootScope, $timeout, $$rAF) {
+ $rootScope.$digest();
+
+ $$rAF.flush(); // flush $animate.enter(backdrop)
+ $timeout.flush(); // flush response
+ $$rAF.flush(); // flush $animateCss
+ $timeout.flush(); // flush response
+
+ $rootScope.$digest();
+ });
+ } catch(e) { }
+ }
+
+ function waitForSelectClose() {
+ try {
+ inject(function($rootScope, $timeout, $$rAF) {
+ $rootScope.$digest();
+
+ $$rAF.flush(); // flush $animate.leave(backdrop)
+ $timeout.flush(); // flush response
+
+ $rootScope.$digest();
+
+ $$rAF.flush();
+ $rootScope.$digest();
+ });
+ } catch(e) { }
+ }
+
});
diff --git a/src/components/toast/toast.js b/src/components/toast/toast.js
index d752560a7b7..9f742a47b34 100644
--- a/src/components/toast/toast.js
+++ b/src/components/toast/toast.js
@@ -11,9 +11,17 @@ angular.module('material.components.toast', [
.directive('mdToast', MdToastDirective)
.provider('$mdToast', MdToastProvider);
-function MdToastDirective() {
+/* @ngInject */
+function MdToastDirective($mdToast) {
return {
- restrict: 'E'
+ restrict: 'E',
+ link: function postLink(scope, element, attr) {
+ // When navigation force destroys an interimElement, then
+ // listen and $destroy() that interim instance...
+ scope.$on('$destroy', function() {
+ $mdToast.destroy();
+ });
+ }
};
}
@@ -254,7 +262,7 @@ function MdToastProvider($$interimElementProvider) {
element.off(SWIPE_EVENTS, options.onSwipe);
options.parent.removeClass(options.openClass);
- return $animate.leave(element);
+ return (options.$destroy == true) ? element.remove() : $animate.leave(element);
}
function toastOpenClass(position) {
diff --git a/src/core/services/interimElement/interimElement.js b/src/core/services/interimElement/interimElement.js
index b12e6c3e83b..2f29b2ccb46 100644
--- a/src/core/services/interimElement/interimElement.js
+++ b/src/core/services/interimElement/interimElement.js
@@ -116,9 +116,14 @@ function InterimElementProvider() {
var publicService = {
hide: interimElementService.hide,
cancel: interimElementService.cancel,
- show: showInterimElement
+ show: showInterimElement,
+
+ // Special internal method to destroy an interim element without animations
+ // used when navigation changes causes a $scope.$destroy() action
+ destroy : destroyInterimElement
};
+
defaultMethods = providerConfig.methods || [];
// This must be invoked after the publicService is initialized
defaultOptions = invokeFactory(providerConfig.optionsFactory, {});
@@ -178,9 +183,11 @@ function InterimElementProvider() {
//
// @example `$mdToast.simple('hello')` // sets options.content to hello
// // because argOption === 'content'
- if (arguments.length && definition.argOption && !angular.isObject(arg) &&
- !angular.isArray(arg)) {
+ if (arguments.length && definition.argOption &&
+ !angular.isObject(arg) && !angular.isArray(arg)) {
+
return (new Preset())[definition.argOption](arg);
+
} else {
return new Preset(arg);
}
@@ -190,6 +197,9 @@ function InterimElementProvider() {
return publicService;
+ /**
+ *
+ */
function showInterimElement(opts) {
// opts is either a preset which stores its options on an _options field,
// or just an object made up of options
@@ -201,6 +211,17 @@ function InterimElementProvider() {
);
}
+ /**
+ * Special method to hide and destroy an interimElement WITHOUT
+ * any 'leave` or hide animations ( an immediate force hide/remove )
+ *
+ * NOTE: This calls the onRemove() subclass method for each component...
+ * which must have code to respond to `options.$destroy == true`
+ */
+ function destroyInterimElement(opts) {
+ return interimElementService.destroy(opts);
+ }
+
/**
* Helper to call $injector.invoke with a local of the factory name for
* this provider.
@@ -219,7 +240,7 @@ function InterimElementProvider() {
}
/* @ngInject */
- function InterimElementFactory($document, $q, $rootScope, $timeout, $rootElement, $animate,
+ function InterimElementFactory($document, $q, $$q, $rootScope, $timeout, $rootElement, $animate,
$mdUtil, $mdCompiler, $mdTheming, $log ) {
return function createInterimElementService() {
var SHOW_CANCELLED = false;
@@ -241,7 +262,8 @@ function InterimElementProvider() {
return service = {
show: show,
hide: hide,
- cancel: cancel
+ cancel: cancel,
+ destroy : destroy
};
/*
@@ -259,7 +281,7 @@ function InterimElementProvider() {
*/
function show(options) {
options = options || {};
- var interimElement = new InterimElement(options);
+ var interimElement = new InterimElement(options || {});
var hideExisting = !options.skipHide && stack.length ? service.hide() : $q.when(true);
// This hide()s only the current interim element before showing the next, new one
@@ -271,7 +293,8 @@ function InterimElementProvider() {
interimElement
.show()
.catch(function( reason ) {
- // $log.error("InterimElement.show() error: " + reason );
+ //$log.error("InterimElement.show() error: " + reason );
+ return reason;
});
});
@@ -311,9 +334,10 @@ function InterimElementProvider() {
function closeElement(interim) {
interim
- .remove(reason || SHOW_CLOSED, false)
+ .remove(reason || SHOW_CLOSED, false, options || { })
.catch(function( reason ) {
- // $log.error("InterimElement.hide() error: " + reason );
+ //$log.error("InterimElement.hide() error: " + reason );
+ return reason;
});
return interim.deferred.promise;
}
@@ -331,19 +355,30 @@ function InterimElementProvider() {
* @returns Promise that will be resolved after the element has been removed.
*
*/
- function cancel(reason) {
+ function cancel(reason, options) {
var interim = stack.shift();
if ( !interim ) return $q.when(reason || SHOW_CANCELLED);
interim
- .remove(reason || SHOW_CANCELLED, true)
+ .remove(reason || SHOW_CANCELLED, true, options || { })
.catch(function( reason ) {
- // $log.error("InterimElement.cancel() error: " + reason );
+ //$log.error("InterimElement.cancel() error: " + reason );
+ return reason;
});
return interim.deferred.promise;
}
+ /*
+ * Special method to quick-remove the interim element without animations
+ */
+ function destroy() {
+ var interim = stack.shift();
+
+ return interim ? interim.remove(SHOW_CANCELLED, false, {'$destroy':true}) :
+ $q.when(SHOW_CANCELLED);
+ }
+
/*
* Internal Interim Element Object
@@ -393,39 +428,44 @@ function InterimElementProvider() {
* - perform the transition-out, and
* - perform optional clean up scope.
*/
- function transitionOutAndRemove(response, isCancelled) {
+ function transitionOutAndRemove(response, isCancelled, opts) {
+ options = angular.merge(options || {}, opts || {});
options.cancelAutoHide && options.cancelAutoHide();
+ options.element.triggerHandler('$mdInterimElementRemove');
- return $q(function(resolve, reject){
+ if ( options.$destroy === true ) {
- $q.when(showAction).finally(function(){
- options.element.triggerHandler('$mdInterimElementRemove');
- hideElement(options.element, options).then( function() {
+ return hideElement(options.element, options);
- (isCancelled && rejectAll(response)) || resolveAll();
+ } else {
- }, rejectAll );
+ $q.when(showAction)
+ .finally(function() {
+ hideElement(options.element, options).then(function() {
- });
+ (isCancelled && rejectAll(response)) || resolveAll(response);
- function resolveAll() {
- // The `show()` returns a promise that will be resolved when the interim
- // element is hidden or cancelled...
- self.deferred.resolve(response);
+ }, rejectAll);
+ });
- // Now resolve the `.hide()` promise itself (optional)
- resolve(response);
- }
+ return self.deferred.promise;
+ }
- function rejectAll(fault) {
- // Force the '$md.show()' promise to reject
- self.deferred.reject(fault);
- // Continue rejection propagation
- reject(fault);
- }
+ /**
+ * The `show()` returns a promise that will be resolved when the interim
+ * element is hidden or cancelled...
+ */
+ function resolveAll(response) {
+ self.deferred.resolve(response);
+ }
- });
+ /**
+ * Force the '$md.show()' promise to reject
+ */
+ function rejectAll(fault) {
+ self.deferred.reject(fault);
+ }
}
/**
@@ -578,21 +618,32 @@ function InterimElementProvider() {
function hideElement(element, options) {
var announceRemoving = options.onRemoving || angular.noop;
- return $q(function (resolve, reject) {
+ return $$q(function (resolve, reject) {
try {
// Start transitionIn
- var action = $q.when(element ? options.onRemove(options.scope, element, options) : true);
+ var action = $$q.when( options.onRemove(options.scope, element, options) || true );
// Trigger callback *before* the remove operation starts
announceRemoving(element, action);
- // Wait until transition-out is done
- action.then(function () {
+ if ( options.$destroy == true ) {
- !options.preserveScope && options.scope.$destroy();
+ // For $destroy, onRemove should be synchronous
resolve(element);
- }, reject );
+ } else {
+
+ // Wait until transition-out is done
+ action.then(function () {
+
+ if (!options.preserveScope && options.scope ) {
+ options.scope.$destroy();
+ }
+
+ resolve(element);
+
+ }, reject );
+ }
} catch(e) {
reject(e.message);