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

Commit b7ae33e

Browse files
EladBezaleljelbourn
authored andcommitted
feat(dialog themes): extended theme inheritance of dialogs (#9762)
- Dialogs now can also inherit promise/function defined themes and listen to the targetElement theme changes - Removed wrong tests, we now assume that if an interpolation is being used in `md-theme` it was meant to be watched, unless one-way binding (::) was applied. Related to #9475
1 parent c93fdad commit b7ae33e

File tree

8 files changed

+336
-52
lines changed

8 files changed

+336
-52
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<md-dialog aria-label="Mango (Fruit)">
2+
<form ng-cloak>
3+
<md-toolbar>
4+
<div class="md-toolbar-tools">
5+
<h2>Mango (Fruit)</h2>
6+
<span flex></span>
7+
<md-button class="md-icon-button" ng-click="cancel()">
8+
<md-icon md-svg-src="img/icons/ic_close_24px.svg" aria-label="Close dialog"></md-icon>
9+
</md-button>
10+
</div>
11+
</md-toolbar>
12+
13+
<md-dialog-content>
14+
<div class="md-dialog-content">
15+
<h2>Using .md-dialog-content class that sets the padding as the spec</h2>
16+
<p>
17+
The mango is a juicy stone fruit belonging to the genus Mangifera, consisting of numerous tropical fruiting trees, cultivated mostly for edible fruit. The majority of these species are found in nature as wild mangoes. They all belong to the flowering plant family Anacardiaceae. The mango is native to South and Southeast Asia, from where it has been distributed worldwide to become one of the most cultivated fruits in the tropics.
18+
</p>
19+
20+
<img style="margin: auto; max-width: 100%;" alt="Lush mango tree" src="img/mangues.jpg">
21+
22+
<p>
23+
The highest concentration of Mangifera genus is in the western part of Malesia (Sumatra, Java and Borneo) and in Burma and India. While other Mangifera species (e.g. horse mango, M. foetida) are also grown on a more localized basis, Mangifera indica&mdash;the "common mango" or "Indian mango"&mdash;is the only mango tree commonly cultivated in many tropical and subtropical regions.
24+
</p>
25+
<p>
26+
It originated in Indian subcontinent (present day India and Pakistan) and Burma. It is the national fruit of India, Pakistan, and the Philippines, and the national tree of Bangladesh. In several cultures, its fruit and leaves are ritually used as floral decorations at weddings, public celebrations, and religious ceremonies.
27+
</p>
28+
</div>
29+
</md-dialog-content>
30+
31+
<md-dialog-actions layout="row">
32+
<md-button href="http://en.wikipedia.org/wiki/Mango" target="_blank" md-autofocus>
33+
More on Wikipedia
34+
</md-button>
35+
<span flex></span>
36+
<md-button ng-click="answer('not useful')">
37+
Not Useful
38+
</md-button>
39+
<md-button ng-click="answer('useful')" class="md-primary">
40+
Useful
41+
</md-button>
42+
</md-dialog-actions>
43+
</form>
44+
</md-dialog>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div ng-controller="AppCtrl" ng-cloak md-theme="{{theme}}" class="container">
2+
<p class="inset">
3+
We have an interval that changes the color of the button from <code>red</code> to <code>blue</code> themes,
4+
Open the dialog to see the theme being inherited to the dialog and any component inside
5+
</p>
6+
<md-button class="md-primary md-raised" ng-click="showAdvanced($event)">Open Dialog</md-button>
7+
</div>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
angular.module('dialogDemo1', ['ngMaterial'])
2+
.config(function ($mdThemingProvider) {
3+
$mdThemingProvider.theme('red')
4+
.primaryPalette('red');
5+
6+
$mdThemingProvider.theme('blue')
7+
.primaryPalette('blue');
8+
9+
})
10+
.controller('AppCtrl', function($scope, $mdDialog, $interval) {
11+
$scope.theme = 'red';
12+
13+
var isThemeRed = true;
14+
15+
$interval(function () {
16+
$scope.theme = isThemeRed ? 'blue' : 'red';
17+
18+
isThemeRed = !isThemeRed;
19+
}, 2000);
20+
21+
$scope.showAdvanced = function(ev) {
22+
$mdDialog.show({
23+
controller: DialogController,
24+
templateUrl: 'dialog1.tmpl.html',
25+
parent: angular.element(document.body),
26+
targetEvent: ev,
27+
clickOutsideToClose:true
28+
})
29+
.then(function(answer) {
30+
$scope.status = 'You said the information was "' + answer + '".';
31+
}, function() {
32+
$scope.status = 'You cancelled the dialog.';
33+
});
34+
};
35+
36+
function DialogController($scope, $mdDialog) {
37+
$scope.hide = function() {
38+
$mdDialog.hide();
39+
};
40+
41+
$scope.cancel = function() {
42+
$mdDialog.cancel();
43+
};
44+
45+
$scope.answer = function(answer) {
46+
$mdDialog.hide(answer);
47+
};
48+
}
49+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.container {
2+
text-align: center;
3+
}

src/components/dialog/dialog.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ function MdDialogProvider($$interimElementProvider) {
588588
function advancedDialogOptions($mdDialog, $mdConstant) {
589589
return {
590590
template: [
591-
'<md-dialog md-theme="{{ dialog.theme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
591+
'<md-dialog md-theme="{{ dialog.theme || dialog.defaultTheme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
592592
' <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',
593593
' <h2 class="md-title">{{ dialog.title }}</h2>',
594594
' <div ng-if="::dialog.mdHtmlContent" class="md-dialog-content-body" ',
@@ -638,7 +638,7 @@ function MdDialogProvider($$interimElementProvider) {
638638

639639
/* @ngInject */
640640
function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
641-
$log, $injector, $mdTheming) {
641+
$log, $injector, $mdTheming, $interpolate) {
642642

643643
return {
644644
hasBackdrop: true,
@@ -661,7 +661,10 @@ function MdDialogProvider($$interimElementProvider) {
661661
// an element outside of the container, and the focus trap won't work probably..
662662
// Also the tabindex is needed for the `escapeToClose` functionality, because
663663
// the keyDown event can't be triggered when the focus is outside of the container.
664-
return '<div class="md-dialog-container" tabindex="-1">' + validatedTemplate(template) + '</div>';
664+
var startSymbol = $interpolate.startSymbol();
665+
var endSymbol = $interpolate.endSymbol();
666+
var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol;
667+
return '<div class="md-dialog-container" tabindex="-1" md-theme="' + theme + '">' + validatedTemplate(template) + '</div>';
665668

666669
/**
667670
* The specified template should contain a <md-dialog> wrapper element....
@@ -680,6 +683,8 @@ function MdDialogProvider($$interimElementProvider) {
680683
// Automatically apply the theme, if the user didn't specify a theme explicitly.
681684
// Those option changes need to be done, before the compilation has started, because otherwise
682685
// the option changes will be not available in the $mdCompilers locales.
686+
options.defaultTheme = $mdTheming.defaultTheme();
687+
683688
detectTheming(options);
684689
}
685690

@@ -798,19 +803,34 @@ function MdDialogProvider($$interimElementProvider) {
798803
}
799804

800805
function detectTheming(options) {
801-
// Only detect the theming, if the developer didn't specify the theme specifically.
802-
if (options.theme) return;
806+
// Once the user specifies a targetEvent, we will automatically try to find the correct
807+
// nested theme.
808+
var targetEl;
809+
if (options.targetEvent && options.targetEvent.target) {
810+
targetEl = angular.element(options.targetEvent.target);
811+
}
803812

804-
options.theme = $mdTheming.defaultTheme();
813+
var themeCtrl = targetEl && targetEl.controller('mdTheme');
805814

806-
if (options.targetEvent && options.targetEvent.target) {
807-
var targetEl = angular.element(options.targetEvent.target);
815+
if (!themeCtrl) {
816+
return;
817+
}
808818

809-
// Once the user specifies a targetEvent, we will automatically try to find the correct
810-
// nested theme.
811-
options.theme = (targetEl.controller('mdTheme') || {}).$mdTheme || options.theme;
819+
options.themeWatch = themeCtrl.$shouldWatch;
820+
821+
var theme = options.theme || themeCtrl.$mdTheme;
822+
823+
if (theme) {
824+
options.scope.theme = theme;
812825
}
813826

827+
var unwatch = themeCtrl.registerChanges(function (newTheme) {
828+
options.scope.theme = newTheme;
829+
830+
if (!options.themeWatch) {
831+
unwatch();
832+
}
833+
});
814834
}
815835

816836
/**

src/components/dialog/dialog.spec.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,85 @@ describe('$mdDialog', function() {
16571657
// Clean up our modifications to the DOM.
16581658
document.body.removeChild(parent);
16591659
});
1660+
1661+
describe('theming', function () {
1662+
it('should inherit targetElement theme', inject(function($mdDialog, $mdTheming, $rootScope, $compile) {
1663+
var template = '<div id="rawContent">Hello</div>';
1664+
var parent = angular.element('<div>');
1665+
1666+
var button = $compile('<button ng-click="showDialog($event)" md-theme="myTheme">test</button>')($rootScope);
1667+
1668+
$mdTheming(button);
1669+
1670+
$rootScope.showDialog = function (ev) {
1671+
$mdDialog.show({
1672+
template: template,
1673+
parent: parent,
1674+
targetEvent: ev
1675+
});
1676+
};
1677+
1678+
button[0].click();
1679+
1680+
var container = parent[0].querySelector('.md-dialog-container');
1681+
var dialog = angular.element(container).find('md-dialog');
1682+
expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy();
1683+
}));
1684+
1685+
it('should watch targetElement theme if it has interpolation', inject(function($mdDialog, $mdTheming, $rootScope, $compile) {
1686+
var template = '<div id="rawContent">Hello</div>';
1687+
var parent = angular.element('<div>');
1688+
1689+
$rootScope.theme = 'myTheme';
1690+
1691+
var button = $compile('<button ng-click="showDialog($event)" md-theme="{{theme}}">test</button>')($rootScope);
1692+
1693+
$mdTheming(button);
1694+
1695+
$rootScope.showDialog = function (ev) {
1696+
$mdDialog.show({
1697+
template: template,
1698+
parent: parent,
1699+
targetEvent: ev
1700+
});
1701+
};
1702+
1703+
button[0].click();
1704+
1705+
var container = parent[0].querySelector('.md-dialog-container');
1706+
var dialog = angular.element(container).find('md-dialog');
1707+
expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy();
1708+
$rootScope.$apply('theme = "anotherTheme"');
1709+
expect(dialog.hasClass('md-anotherTheme-theme')).toBeTruthy();
1710+
}));
1711+
1712+
it('should resolve targetElement theme if it\'s a function', inject(function($mdDialog, $mdTheming, $rootScope, $compile) {
1713+
var template = '<div id="rawContent">Hello</div>';
1714+
var parent = angular.element('<div>');
1715+
1716+
$rootScope.theme = function () {
1717+
return 'myTheme';
1718+
};
1719+
1720+
var button = $compile('<button ng-click="showDialog($event)" md-theme="theme">test</button>')($rootScope);
1721+
1722+
$mdTheming(button);
1723+
1724+
$rootScope.showDialog = function (ev) {
1725+
$mdDialog.show({
1726+
template: template,
1727+
parent: parent,
1728+
targetEvent: ev
1729+
});
1730+
};
1731+
1732+
button[0].click();
1733+
1734+
var container = parent[0].querySelector('.md-dialog-container');
1735+
var dialog = angular.element(container).find('md-dialog');
1736+
expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy();
1737+
}));
1738+
});
16601739
});
16611740

16621741
function hasConfigurationMethods(preset, methods) {

src/core/services/theming/theming.js

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,11 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
624624
return angular.extend({}, PALETTES);
625625
}
626626
});
627+
Object.defineProperty(applyTheme, 'ALWAYS_WATCH', {
628+
get: function () {
629+
return alwaysWatchTheme;
630+
}
631+
});
627632
applyTheme.inherit = inheritTheme;
628633
applyTheme.registered = registered;
629634
applyTheme.defaultTheme = function() { return defaultTheme; };
@@ -674,8 +679,9 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
674679
updateThemeClass(lookupThemeName());
675680

676681
if (ctrl) {
677-
var watchTheme =
678-
alwaysWatchTheme || ctrl.$shouldWatch || $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
682+
var watchTheme = alwaysWatchTheme ||
683+
ctrl.$shouldWatch ||
684+
$mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
679685

680686
var unwatch = ctrl.registerChanges(function (name) {
681687
updateThemeClass(name);
@@ -723,10 +729,27 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
723729

724730
function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
725731
return {
726-
priority: 100,
732+
priority: 101, // has to be more than 100 to be before interpolation (issue on IE)
727733
link: {
728734
pre: function(scope, el, attrs) {
729735
var registeredCallbacks = [];
736+
737+
var startSymbol = $interpolate.startSymbol();
738+
var endSymbol = $interpolate.endSymbol();
739+
740+
var theme = attrs.mdTheme.trim();
741+
742+
var hasInterpolation =
743+
theme.substr(0, startSymbol.length) === startSymbol &&
744+
theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length;
745+
746+
var oneTimeOperator = '::';
747+
var oneTimeBind = attrs.mdTheme
748+
.split(startSymbol).join('')
749+
.split(endSymbol).join('')
750+
.trim()
751+
.substr(0, oneTimeOperator.length) === oneTimeOperator;
752+
730753
var ctrl = {
731754
registerChanges: function (cb, context) {
732755
if (context) {
@@ -748,38 +771,49 @@ function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
748771
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
749772
}
750773

751-
752774
ctrl.$mdTheme = theme;
753775

754776
// Iterating backwards to support unregistering during iteration
755777
// http://stackoverflow.com/a/9882349/890293
756-
registeredCallbacks.reverse().forEach(function (cb) {
757-
cb(theme);
758-
});
778+
// we don't use `reverse()` of array because it mutates the array and we don't want it to get re-indexed
779+
for (var i = registeredCallbacks.length; i--;) {
780+
registeredCallbacks[i](theme);
781+
}
759782
},
760-
$shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'))
783+
$shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) ||
784+
$mdTheming.ALWAYS_WATCH ||
785+
(hasInterpolation && !oneTimeBind)
761786
};
762787

763788
el.data('$mdThemeController', ctrl);
764789

765-
var getThemeInterpolation = function () { return $interpolate(attrs.mdTheme)(scope); };
766-
767-
var setParsedTheme = function (interpolation) {
768-
var theme = $parse(interpolation)(scope) || interpolation;
790+
var getTheme = function () {
791+
var interpolation = $interpolate(attrs.mdTheme)(scope);
792+
return $parse(interpolation)(scope) || interpolation;
793+
};
769794

795+
var setParsedTheme = function (theme) {
770796
if (typeof theme === 'string') {
771797
return ctrl.$setTheme(theme);
772798
}
773799

774-
$q.when( (typeof theme === 'function') ? theme() : theme )
800+
$q.when( angular.isFunction(theme) ? theme() : theme )
775801
.then(function(name){
776-
ctrl.$setTheme(name)
802+
ctrl.$setTheme(name);
777803
});
778804
};
779805

780-
setParsedTheme(getThemeInterpolation());
806+
setParsedTheme(getTheme());
781807

782-
scope.$watch(getThemeInterpolation, setParsedTheme);
808+
var unwatch = scope.$watch(getTheme, function(theme) {
809+
if (theme) {
810+
setParsedTheme(theme);
811+
812+
if (!ctrl.$shouldWatch) {
813+
unwatch();
814+
}
815+
}
816+
});
783817
}
784818
}
785819
};

0 commit comments

Comments
 (0)