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

Commit 9f3b8fa

Browse files
committed
feat(dialog themes): extended theme inheritance of dialogs
- 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 40b4de4 commit 9f3b8fa

File tree

8 files changed

+317
-39
lines changed

8 files changed

+317
-39
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

@@ -811,19 +816,34 @@ function MdDialogProvider($$interimElementProvider) {
811816
}
812817

813818
function detectTheming(options) {
814-
// Only detect the theming, if the developer didn't specify the theme specifically.
815-
if (options.theme) return;
819+
// Once the user specifies a targetEvent, we will automatically try to find the correct
820+
// nested theme.
821+
var targetEl;
822+
if (options.targetEvent && options.targetEvent.target) {
823+
targetEl = angular.element(options.targetEvent.target);
824+
}
816825

817-
options.theme = $mdTheming.defaultTheme();
826+
var themeCtrl = targetEl && targetEl.controller('mdTheme');
818827

819-
if (options.targetEvent && options.targetEvent.target) {
820-
var targetEl = angular.element(options.targetEvent.target);
828+
if (!themeCtrl) {
829+
return;
830+
}
821831

822-
// Once the user specifies a targetEvent, we will automatically try to find the correct
823-
// nested theme.
824-
options.theme = (targetEl.controller('mdTheme') || {}).$mdTheme || options.theme;
832+
options.themeWatch = themeCtrl.$shouldWatch;
833+
834+
var theme = options.theme || themeCtrl.$mdTheme;
835+
836+
if (theme) {
837+
options.scope.theme = theme;
825838
}
826839

840+
var unwatch = themeCtrl.registerChanges(function (newTheme) {
841+
options.scope.theme = newTheme;
842+
843+
if (!options.themeWatch) {
844+
unwatch();
845+
}
846+
});
827847
}
828848

829849
/**

src/components/dialog/dialog.spec.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,88 @@ describe('$mdDialog', function() {
16761676
// Clean up our modifications to the DOM.
16771677
document.body.removeChild(parent);
16781678
});
1679+
1680+
describe('theming', function () {
1681+
it('should inherit targetElement theme', inject(function($mdDialog, $mdTheming, $rootScope, $compile) {
1682+
var template = '<div id="rawContent">Hello</div>';
1683+
var parent = angular.element('<div>');
1684+
1685+
var button = $compile('<button ng-click="showDialog($event)" md-theme="myTheme">test</button>')($rootScope);
1686+
1687+
$mdTheming(button);
1688+
1689+
$rootScope.showDialog = function (ev) {
1690+
$mdDialog.show({
1691+
template: template,
1692+
parent: parent,
1693+
targetEvent: ev
1694+
});
1695+
};
1696+
1697+
button[0].click();
1698+
$rootScope.$apply();
1699+
1700+
var container = parent[0].querySelector('.md-dialog-container');
1701+
var dialog = angular.element(container).find('md-dialog');
1702+
expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy();
1703+
}));
1704+
1705+
it('should watch targetElement theme if it has interpolation', inject(function($mdDialog, $mdTheming, $rootScope, $compile) {
1706+
var template = '<div id="rawContent">Hello</div>';
1707+
var parent = angular.element('<div>');
1708+
1709+
$rootScope.theme = 'myTheme';
1710+
1711+
var button = $compile('<button ng-click="showDialog($event)" md-theme="{{theme}}">test</button>')($rootScope);
1712+
1713+
$mdTheming(button);
1714+
1715+
$rootScope.showDialog = function (ev) {
1716+
$mdDialog.show({
1717+
template: template,
1718+
parent: parent,
1719+
targetEvent: ev
1720+
});
1721+
};
1722+
1723+
button[0].click();
1724+
$rootScope.$apply();
1725+
1726+
var container = parent[0].querySelector('.md-dialog-container');
1727+
var dialog = angular.element(container).find('md-dialog');
1728+
expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy();
1729+
$rootScope.$apply('theme = "anotherTheme"');
1730+
expect(dialog.hasClass('md-anotherTheme-theme')).toBeTruthy();
1731+
}));
1732+
1733+
it('should resolve targetElement theme if it\'s a function', inject(function($mdDialog, $mdTheming, $rootScope, $compile) {
1734+
var template = '<div id="rawContent">Hello</div>';
1735+
var parent = angular.element('<div>');
1736+
1737+
$rootScope.theme = function () {
1738+
return 'myTheme';
1739+
};
1740+
1741+
var button = $compile('<button ng-click="showDialog($event)" md-theme="theme">test</button>')($rootScope);
1742+
1743+
$mdTheming(button);
1744+
1745+
$rootScope.showDialog = function (ev) {
1746+
$mdDialog.show({
1747+
template: template,
1748+
parent: parent,
1749+
targetEvent: ev
1750+
});
1751+
};
1752+
1753+
button[0].click();
1754+
$rootScope.$apply();
1755+
1756+
var container = parent[0].querySelector('.md-dialog-container');
1757+
var dialog = angular.element(container).find('md-dialog');
1758+
expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy();
1759+
}));
1760+
});
16791761
});
16801762

16811763
function hasConfigurationMethods(preset, methods) {

src/core/services/theming/theming.js

Lines changed: 45 additions & 12 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; };
@@ -727,6 +732,23 @@ function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
727732
link: {
728733
pre: function(scope, el, attrs) {
729734
var registeredCallbacks = [];
735+
736+
var startSymbol = $interpolate.startSymbol();
737+
var endSymbol = $interpolate.endSymbol();
738+
739+
var theme = attrs.mdTheme.trim();
740+
741+
var hasInterpolation =
742+
theme.substr(0, startSymbol.length) === startSymbol &&
743+
theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length;
744+
745+
var oneTimeOperator = '::';
746+
var oneTimeBind = attrs.mdTheme
747+
.split(startSymbol).join('')
748+
.split(endSymbol).join('')
749+
.trim()
750+
.substr(0, oneTimeOperator.length) === oneTimeOperator;
751+
730752
var ctrl = {
731753
registerChanges: function (cb, context) {
732754
if (context) {
@@ -748,38 +770,49 @@ function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
748770
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
749771
}
750772

751-
752773
ctrl.$mdTheme = theme;
753774

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

763787
el.data('$mdThemeController', ctrl);
764788

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

794+
var setParsedTheme = function (theme) {
770795
if (typeof theme === 'string') {
771796
return ctrl.$setTheme(theme);
772797
}
773798

774799
$q.when( (typeof theme === 'function') ? theme() : theme )
775800
.then(function(name){
776-
ctrl.$setTheme(name)
801+
ctrl.$setTheme(name);
777802
});
778803
};
779804

780-
setParsedTheme(getThemeInterpolation());
805+
setParsedTheme(getTheme());
781806

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

0 commit comments

Comments
 (0)