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

Commit 8faa199

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 3854c4a commit 8faa199

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
@@ -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, $mdUtil, $q, $log) {
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 $q.resolve(name);
587+
};
555588
applyTheme.setBrowserColor = enableBrowserColor;
556589

557590
return applyTheme;
@@ -568,22 +601,31 @@ 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 =
610+
alwaysWatchTheme || ctrl.$shouldWatch || $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch'));
611+
612+
var unwatch = ctrl.registerChanges(function (name) {
613+
updateThemeClass(name);
614+
615+
if (!watchTheme) {
616+
unwatch();
617+
}
618+
else {
619+
el.on('$destroy', unwatch);
620+
}
621+
});
579622
}
580623

581624
/**
582625
* Find the theme name from the parent controller or element data
583626
*/
584627
function lookupThemeName() {
585628
// 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');
587629
return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme);
588630
}
589631

@@ -606,24 +648,12 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
606648
el.data('$mdThemeController', ctrl);
607649
}
608650
}
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-
}
621651
}
622652

623653
}
624654
}
625655

626-
function ThemingDirective($mdTheming, $interpolate, $log) {
656+
function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) {
627657
return {
628658
priority: 100,
629659
link: {
@@ -649,16 +679,39 @@ function ThemingDirective($mdTheming, $interpolate, $log) {
649679
if (!$mdTheming.registered(theme)) {
650680
$log.warn('attempted to use unregistered theme \'' + theme + '\'');
651681
}
682+
683+
652684
ctrl.$mdTheme = theme;
653685

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

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)