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

Commit f6e667e

Browse files
committed
feat(themes): register theme on the fly
- Added `defineTheme` functionality to `$mdTheming` service - Added support for promise and a function that returns a promise on `md-theme` directive to support async build of themes fixes #2965
1 parent 0cd2a59 commit f6e667e

File tree

2 files changed

+126
-28
lines changed

2 files changed

+126
-28
lines changed

src/core/services/theming/theming.js

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -538,20 +538,53 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
538538
* @param {el=} element to apply theming to
539539
*/
540540
/* @ngInject */
541-
function ThemingService($rootScope, $log) {
541+
function ThemingService($rootScope, $log, $mdUtil) {
542542
// Allow us to be invoked via a linking function signature.
543543
var applyTheme = function (scope, el) {
544544
if (el === undefined) { el = scope; scope = undefined; }
545545
if (scope === undefined) { scope = $rootScope; }
546546
applyTheme.inherit(el, el);
547547
};
548548

549-
applyTheme.THEMES = angular.extend({}, THEMES);
550-
applyTheme.PALETTES = angular.extend({}, PALETTES);
549+
Object.defineProperty(applyTheme, 'THEMES', {
550+
get: function () {
551+
return angular.extend({}, THEMES);
552+
}
553+
});
554+
Object.defineProperty(applyTheme, 'PALETTES', {
555+
get: function () {
556+
return angular.extend({}, PALETTES);
557+
}
558+
});
551559
applyTheme.inherit = inheritTheme;
552560
applyTheme.registered = registered;
553561
applyTheme.defaultTheme = function() { return defaultTheme; };
554562
applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
563+
applyTheme.defineTheme = function (name, options) {
564+
options = options || {};
565+
566+
var theme = registerTheme(name);
567+
568+
if (options.primary) {
569+
theme.primaryPalette(options.primary);
570+
}
571+
if (options.accent) {
572+
theme.accentPalette(options.accent);
573+
}
574+
if (options.warn) {
575+
theme.warnPalette(options.warn);
576+
}
577+
if (options.background) {
578+
theme.backgroundPalette(options.background);
579+
}
580+
if (options.dark){
581+
theme.dark();
582+
}
583+
584+
this.generateTheme(name);
585+
586+
return theme;
587+
};
555588
applyTheme.setBrowserColor = enableBrowserColor;
556589

557590
return applyTheme;
@@ -568,22 +601,30 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
568601
* Get theme name for the element, then update with Theme CSS class
569602
*/
570603
function inheritTheme (el, parent) {
571-
var ctrl = parent.controller('mdTheme');
572-
var attrThemeValue = el.attr('md-theme-watch');
573-
var watchTheme = (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false';
604+
var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
574605

575606
updateThemeClass(lookupThemeName());
576607

577-
if ((alwaysWatchTheme && !registerChangeCallback()) || (!alwaysWatchTheme && watchTheme)) {
578-
el.on('$destroy', $rootScope.$watch(lookupThemeName, updateThemeClass) );
608+
if (ctrl) {
609+
var watchTheme = alwaysWatchTheme || ctrl.$shouldWatch || $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
610+
611+
var unwatch = ctrl.registerChanges(function (name) {
612+
updateThemeClass(name);
613+
614+
if (!watchTheme) {
615+
unwatch();
616+
}
617+
else {
618+
el.on('$destroy', unwatch);
619+
}
620+
});
579621
}
580622

581623
/**
582624
* Find the theme name from the parent controller or element data
583625
*/
584626
function lookupThemeName() {
585627
// As a few components (dialog) add their controllers later, we should also watch for a controller init.
586-
ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
587628
return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
588629
}
589630

@@ -606,24 +647,12 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
606647
el.data('$mdThemeController', ctrl);
607648
}
608649
}
609-
610-
/**
611-
* Register change callback with parent mdTheme controller
612-
*/
613-
function registerChangeCallback() {
614-
var parentController = parent.controller('mdTheme');
615-
if (!parentController) return false;
616-
el.on('$destroy', parentController.registerChanges( function() {
617-
updateThemeClass(lookupThemeName());
618-
}));
619-
return true;
620-
}
621650
}
622651

623652
}
624653
}
625654

626-
function ThemingDirective($mdTheming, $interpolate, $log) {
655+
function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
627656
return {
628657
priority: 100,
629658
link: {
@@ -649,16 +678,39 @@ function ThemingDirective($mdTheming, $interpolate, $log) {
649678
if (!$mdTheming.registered(theme)) {
650679
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
651680
}
681+
682+
652683
ctrl.$mdTheme = theme;
653684

654-
registeredCallbacks.forEach(function (cb) {
655-
cb();
685+
// Iterating backwards to support unregistering during iteration
686+
// http://stackoverflow.com/a/9882349/890293
687+
registeredCallbacks.reverse().forEach(function (cb) {
688+
cb(theme);
656689
});
657-
}
690+
},
691+
$shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'))
658692
};
693+
659694
el.data('$mdThemeController', ctrl);
660-
ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));
661-
attrs.$observe('mdTheme', ctrl.$setTheme);
695+
696+
var getThemeInterpolation = function () { return $interpolate(attrs.mdTheme)(scope); };
697+
698+
var setParsedTheme = function (interpolation) {
699+
var theme = $parse(interpolation)(scope) || interpolation;
700+
701+
if (typeof theme === 'string') {
702+
return ctrl.$setTheme(theme);
703+
}
704+
705+
$q.when( (typeof theme === 'function') ? theme() : theme )
706+
.then(function(name){
707+
ctrl.$setTheme(name)
708+
});
709+
};
710+
711+
setParsedTheme(getThemeInterpolation());
712+
713+
scope.$watch(getThemeInterpolation, setParsedTheme);
662714
}
663715
}
664716
};

src/core/services/theming/theming.spec.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ describe('$mdThemeProvider with custom styles', function() {
453453
// Verify that $MD_THEME_CSS is still set to '/**/' in the test environment.
454454
// Check angular-material-mocks.js for $MD_THEME_CSS latest value if this test starts to fail.
455455
expect($MD_THEME_CSS).toBe('/**/');
456+
expect($MD_THEME_CSS).toBe('/**/');
456457
});
457458

458459
// Find the string '/**//*test*/' in the head tag.
@@ -737,6 +738,17 @@ describe('$mdTheming service', function() {
737738
expect($mdTheming.defaultTheme()).toBe('default');
738739
}));
739740

741+
it('supports registering theme on the fly', inject(function ($mdTheming) {
742+
expect($mdTheming.THEMES.hasOwnProperty('test')).toBeFalsy();
743+
744+
$mdTheming.defineTheme('test', {
745+
primary: 'red',
746+
warn: 'yellow'
747+
});
748+
749+
expect($mdTheming.THEMES.hasOwnProperty('test')).toBeTruthy();
750+
}));
751+
740752
it('supports changing browser color on the fly', function() {
741753
var name = 'theme-color';
742754
var primaryPalette = $mdThemingProvider._THEMES.default.colors.primary.name;
@@ -758,7 +770,7 @@ describe('$mdTheming service', function() {
758770
describe('md-theme directive', function() {
759771
beforeEach(module('material.core'));
760772

761-
it('should observe and set mdTheme controller', inject(function($compile, $rootScope) {
773+
it('should watch and set mdTheme controller', inject(function($compile, $rootScope) {
762774
$rootScope.themey = 'red';
763775
var el = $compile('<div md-theme="{{themey}}">')($rootScope);
764776
$rootScope.$apply();
@@ -785,6 +797,40 @@ describe('md-theme directive', function() {
785797
expect($log.warn.calls.count()).toBe(0);
786798
});
787799
});
800+
801+
it('should accept string as a theme', function() {
802+
inject(function($compile, $rootScope, $mdTheming) {
803+
var el = $compile('<div md-theme="red"></div>')($rootScope);
804+
$rootScope.$apply();
805+
$mdTheming(el);
806+
expect(el.hasClass('md-default-theme')).toBeFalsy();
807+
expect(el.hasClass('md-red-theme')).toBeTruthy();
808+
});
809+
});
810+
811+
it('should accept $q promise as a theme', function() {
812+
inject(function($compile, $rootScope, $q, $mdTheming) {
813+
$rootScope.promise = $q.resolve('red');
814+
var el = $compile('<div md-theme="promise"></div>')($rootScope);
815+
$mdTheming(el);
816+
$rootScope.$apply();
817+
expect(el.hasClass('md-default-theme')).toBeFalsy();
818+
expect(el.hasClass('md-red-theme')).toBeTruthy();
819+
});
820+
});
821+
822+
it('should accept a function that returns a promise as a theme', function() {
823+
inject(function($compile, $rootScope, $q, $mdTheming) {
824+
$rootScope.func = function () {
825+
return $q.resolve('red');
826+
};
827+
var el = $compile('<div md-theme="func"></div>')($rootScope);
828+
$mdTheming(el);
829+
$rootScope.$apply();
830+
expect(el.hasClass('md-default-theme')).toBeFalsy();
831+
expect(el.hasClass('md-red-theme')).toBeTruthy();
832+
});
833+
});
788834
});
789835

790836
describe('md-themable directive', function() {

0 commit comments

Comments
 (0)