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

Commit 7090a1f

Browse files
EladBezalelkara
authored andcommitted
feat(themes): register theme on the fly (#9475)
- 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 bf5c036 commit 7090a1f

File tree

2 files changed

+132
-43
lines changed

2 files changed

+132
-43
lines changed

src/core/services/theming/theming.js

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -606,20 +606,53 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
606606
*/
607607

608608
/* @ngInject */
609-
function ThemingService($rootScope, $log) {
609+
function ThemingService($rootScope, $mdUtil, $q, $log) {
610610
// Allow us to be invoked via a linking function signature.
611611
var applyTheme = function (scope, el) {
612612
if (el === undefined) { el = scope; scope = undefined; }
613613
if (scope === undefined) { scope = $rootScope; }
614614
applyTheme.inherit(el, el);
615615
};
616616

617-
applyTheme.THEMES = angular.extend({}, THEMES);
618-
applyTheme.PALETTES = angular.extend({}, PALETTES);
617+
Object.defineProperty(applyTheme, 'THEMES', {
618+
get: function () {
619+
return angular.extend({}, THEMES);
620+
}
621+
});
622+
Object.defineProperty(applyTheme, 'PALETTES', {
623+
get: function () {
624+
return angular.extend({}, PALETTES);
625+
}
626+
});
619627
applyTheme.inherit = inheritTheme;
620628
applyTheme.registered = registered;
621629
applyTheme.defaultTheme = function() { return defaultTheme; };
622630
applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
631+
applyTheme.defineTheme = function(name, options) {
632+
options = options || {};
633+
634+
var theme = registerTheme(name);
635+
636+
if (options.primary) {
637+
theme.primaryPalette(options.primary);
638+
}
639+
if (options.accent) {
640+
theme.accentPalette(options.accent);
641+
}
642+
if (options.warn) {
643+
theme.warnPalette(options.warn);
644+
}
645+
if (options.background) {
646+
theme.backgroundPalette(options.background);
647+
}
648+
if (options.dark){
649+
theme.dark();
650+
}
651+
652+
this.generateTheme(name);
653+
654+
return $q.resolve(name);
655+
};
623656
applyTheme.setBrowserColor = enableBrowserColor;
624657

625658
return applyTheme;
@@ -636,22 +669,31 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
636669
* Get theme name for the element, then update with Theme CSS class
637670
*/
638671
function inheritTheme (el, parent) {
639-
var ctrl = parent.controller('mdTheme');
640-
var attrThemeValue = el.attr('md-theme-watch');
641-
var watchTheme = (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false';
672+
var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
642673

643674
updateThemeClass(lookupThemeName());
644675

645-
if ((alwaysWatchTheme && !registerChangeCallback()) || (!alwaysWatchTheme && watchTheme)) {
646-
el.on('$destroy', $rootScope.$watch(lookupThemeName, updateThemeClass) );
676+
if (ctrl) {
677+
var watchTheme =
678+
alwaysWatchTheme || ctrl.$shouldWatch || $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
679+
680+
var unwatch = ctrl.registerChanges(function (name) {
681+
updateThemeClass(name);
682+
683+
if (!watchTheme) {
684+
unwatch();
685+
}
686+
else {
687+
el.on('$destroy', unwatch);
688+
}
689+
});
647690
}
648691

649692
/**
650693
* Find the theme name from the parent controller or element data
651694
*/
652695
function lookupThemeName() {
653696
// As a few components (dialog) add their controllers later, we should also watch for a controller init.
654-
ctrl = parent.controller('mdTheme') || el.data('$mdThemeController');
655697
return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
656698
}
657699

@@ -674,24 +716,12 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
674716
el.data('$mdThemeController', ctrl);
675717
}
676718
}
677-
678-
/**
679-
* Register change callback with parent mdTheme controller
680-
*/
681-
function registerChangeCallback() {
682-
var parentController = parent.controller('mdTheme');
683-
if (!parentController) return false;
684-
el.on('$destroy', parentController.registerChanges( function() {
685-
updateThemeClass(lookupThemeName());
686-
}));
687-
return true;
688-
}
689719
}
690720

691721
}
692722
}
693723

694-
function ThemingDirective($mdTheming, $interpolate, $log) {
724+
function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
695725
return {
696726
priority: 100,
697727
link: {
@@ -717,16 +747,39 @@ function ThemingDirective($mdTheming, $interpolate, $log) {
717747
if (!$mdTheming.registered(theme)) {
718748
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
719749
}
750+
751+
720752
ctrl.$mdTheme = theme;
721753

722-
registeredCallbacks.forEach(function (cb) {
723-
cb();
754+
// Iterating backwards to support unregistering during iteration
755+
// http://stackoverflow.com/a/9882349/890293
756+
registeredCallbacks.reverse().forEach(function (cb) {
757+
cb(theme);
724758
});
725-
}
759+
},
760+
$shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'))
726761
};
762+
727763
el.data('$mdThemeController', ctrl);
728-
ctrl.$setTheme($interpolate(attrs.mdTheme)(scope));
729-
attrs.$observe('mdTheme', ctrl.$setTheme);
764+
765+
var getThemeInterpolation = function () { return $interpolate(attrs.mdTheme)(scope); };
766+
767+
var setParsedTheme = function (interpolation) {
768+
var theme = $parse(interpolation)(scope) || interpolation;
769+
770+
if (typeof theme === 'string') {
771+
return ctrl.$setTheme(theme);
772+
}
773+
774+
$q.when( (typeof theme === 'function') ? theme() : theme )
775+
.then(function(name){
776+
ctrl.$setTheme(name)
777+
});
778+
};
779+
780+
setParsedTheme(getThemeInterpolation());
781+
782+
scope.$watch(getThemeInterpolation, setParsedTheme);
730783
}
731784
}
732785
};

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

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,17 @@ describe('$mdTheming service', function() {
737737
expect($mdTheming.defaultTheme()).toBe('default');
738738
}));
739739

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

761-
it('should observe and set mdTheme controller', inject(function($compile, $rootScope) {
772+
it('should watch and set mdTheme controller', inject(function($compile, $rootScope) {
762773
$rootScope.themey = 'red';
763774
var el = $compile('<div md-theme="{{themey}}">')($rootScope);
764775
$rootScope.$apply();
@@ -768,23 +779,48 @@ describe('md-theme directive', function() {
768779
expect(ctrl.$mdTheme).toBe('blue');
769780
}));
770781

771-
it('warns when an unregistered theme is used', function() {
772-
inject(function($log, $compile, $rootScope) {
773-
spyOn($log, 'warn');
774-
$compile('<div md-theme="unregistered"></div>')($rootScope);
775-
$rootScope.$apply();
776-
expect($log.warn).toHaveBeenCalled();
777-
});
778-
});
782+
it('warns when an unregistered theme is used', inject(function ($log, $compile, $rootScope) {
783+
spyOn($log, 'warn');
784+
$compile('<div md-theme="unregistered"></div>')($rootScope);
785+
$rootScope.$apply();
786+
expect($log.warn).toHaveBeenCalled();
787+
}));
788+
789+
it('does not warn when a registered theme is used', inject(function($log, $compile, $rootScope) {
790+
spyOn($log, 'warn');
791+
$compile('<div md-theme="default"></div>')($rootScope);
792+
$rootScope.$apply();
793+
expect($log.warn.calls.count()).toBe(0);
794+
}));
779795

780-
it('does not warn when a registered theme is used', function() {
781-
inject(function($log, $compile, $rootScope) {
782-
spyOn($log, 'warn');
783-
$compile('<div md-theme="default"></div>')($rootScope);
796+
it('should accept string as a theme', inject(function($compile, $rootScope, $mdTheming) {
797+
var el = $compile('<div md-theme="red"></div>')($rootScope);
798+
$rootScope.$apply();
799+
$mdTheming(el);
800+
expect(el.hasClass('md-default-theme')).toBeFalsy();
801+
expect(el.hasClass('md-red-theme')).toBeTruthy();
802+
}));
803+
804+
it('should accept $q promise as a theme', inject(function($compile, $rootScope, $q, $mdTheming) {
805+
$rootScope.promise = $mdTheming.defineTheme('red', { primary: 'red' });
806+
var el = $compile('<div md-theme="promise"></div>')($rootScope);
807+
$mdTheming(el);
808+
$rootScope.$apply();
809+
expect(el.hasClass('md-default-theme')).toBeFalsy();
810+
expect(el.hasClass('md-red-theme')).toBeTruthy();
811+
}));
812+
813+
it('should accept a function that returns a promise as a theme',
814+
inject(function ($compile, $rootScope, $q, $mdTheming) {
815+
$rootScope.func = function () {
816+
return $mdTheming.defineTheme('red', {primary: 'red'});
817+
};
818+
var el = $compile('<div md-theme="func"></div>')($rootScope);
819+
$mdTheming(el);
784820
$rootScope.$apply();
785-
expect($log.warn.calls.count()).toBe(0);
786-
});
787-
});
821+
expect(el.hasClass('md-default-theme')).toBeFalsy();
822+
expect(el.hasClass('md-red-theme')).toBeTruthy();
823+
}));
788824
});
789825

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

0 commit comments

Comments
 (0)