diff --git a/src/core/services/theming/theming.js b/src/core/services/theming/theming.js index c378df9b3bc..3fe8fe42a1a 100644 --- a/src/core/services/theming/theming.js +++ b/src/core/services/theming/theming.js @@ -606,7 +606,7 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { */ /* @ngInject */ - function ThemingService($rootScope, $log) { + function ThemingService($rootScope, $mdUtil, $q, $log) { // Allow us to be invoked via a linking function signature. var applyTheme = function (scope, el) { if (el === undefined) { el = scope; scope = undefined; } @@ -614,12 +614,45 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { applyTheme.inherit(el, el); }; - applyTheme.THEMES = angular.extend({}, THEMES); - applyTheme.PALETTES = angular.extend({}, PALETTES); + Object.defineProperty(applyTheme, 'THEMES', { + get: function () { + return angular.extend({}, THEMES); + } + }); + Object.defineProperty(applyTheme, 'PALETTES', { + get: function () { + return angular.extend({}, PALETTES); + } + }); applyTheme.inherit = inheritTheme; applyTheme.registered = registered; applyTheme.defaultTheme = function() { return defaultTheme; }; applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); }; + applyTheme.defineTheme = function(name, options) { + options = options || {}; + + var theme = registerTheme(name); + + if (options.primary) { + theme.primaryPalette(options.primary); + } + if (options.accent) { + theme.accentPalette(options.accent); + } + if (options.warn) { + theme.warnPalette(options.warn); + } + if (options.background) { + theme.backgroundPalette(options.background); + } + if (options.dark){ + theme.dark(); + } + + this.generateTheme(name); + + return $q.resolve(name); + }; applyTheme.setBrowserColor = enableBrowserColor; return applyTheme; @@ -636,14 +669,24 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { * Get theme name for the element, then update with Theme CSS class */ function inheritTheme (el, parent) { - var ctrl = parent.controller('mdTheme'); - var attrThemeValue = el.attr('md-theme-watch'); - var watchTheme = (alwaysWatchTheme || angular.isDefined(attrThemeValue)) && attrThemeValue != 'false'; + var ctrl = parent.controller('mdTheme') || el.data('$mdThemeController'); updateThemeClass(lookupThemeName()); - if ((alwaysWatchTheme && !registerChangeCallback()) || (!alwaysWatchTheme && watchTheme)) { - el.on('$destroy', $rootScope.$watch(lookupThemeName, updateThemeClass) ); + if (ctrl) { + var watchTheme = + alwaysWatchTheme || ctrl.$shouldWatch || $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')); + + var unwatch = ctrl.registerChanges(function (name) { + updateThemeClass(name); + + if (!watchTheme) { + unwatch(); + } + else { + el.on('$destroy', unwatch); + } + }); } /** @@ -651,7 +694,6 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { */ function lookupThemeName() { // As a few components (dialog) add their controllers later, we should also watch for a controller init. - ctrl = parent.controller('mdTheme') || el.data('$mdThemeController'); return ctrl && ctrl.$mdTheme || (defaultTheme == 'default' ? '' : defaultTheme); } @@ -674,24 +716,12 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { el.data('$mdThemeController', ctrl); } } - - /** - * Register change callback with parent mdTheme controller - */ - function registerChangeCallback() { - var parentController = parent.controller('mdTheme'); - if (!parentController) return false; - el.on('$destroy', parentController.registerChanges( function() { - updateThemeClass(lookupThemeName()); - })); - return true; - } } } } -function ThemingDirective($mdTheming, $interpolate, $log) { +function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) { return { priority: 100, link: { @@ -717,16 +747,39 @@ function ThemingDirective($mdTheming, $interpolate, $log) { if (!$mdTheming.registered(theme)) { $log.warn('attempted to use unregistered theme \'' + theme + '\''); } + + ctrl.$mdTheme = theme; - registeredCallbacks.forEach(function (cb) { - cb(); + // Iterating backwards to support unregistering during iteration + // http://stackoverflow.com/a/9882349/890293 + registeredCallbacks.reverse().forEach(function (cb) { + cb(theme); }); - } + }, + $shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) }; + el.data('$mdThemeController', ctrl); - ctrl.$setTheme($interpolate(attrs.mdTheme)(scope)); - attrs.$observe('mdTheme', ctrl.$setTheme); + + var getThemeInterpolation = function () { return $interpolate(attrs.mdTheme)(scope); }; + + var setParsedTheme = function (interpolation) { + var theme = $parse(interpolation)(scope) || interpolation; + + if (typeof theme === 'string') { + return ctrl.$setTheme(theme); + } + + $q.when( (typeof theme === 'function') ? theme() : theme ) + .then(function(name){ + ctrl.$setTheme(name) + }); + }; + + setParsedTheme(getThemeInterpolation()); + + scope.$watch(getThemeInterpolation, setParsedTheme); } } }; diff --git a/src/core/services/theming/theming.spec.js b/src/core/services/theming/theming.spec.js index 3066421f444..a29b99ee6da 100644 --- a/src/core/services/theming/theming.spec.js +++ b/src/core/services/theming/theming.spec.js @@ -737,6 +737,17 @@ describe('$mdTheming service', function() { expect($mdTheming.defaultTheme()).toBe('default'); })); + it('supports registering theme on the fly', inject(function ($mdTheming) { + expect($mdTheming.THEMES.hasOwnProperty('test')).toBeFalsy(); + + $mdTheming.defineTheme('test', { + primary: 'red', + warn: 'yellow' + }); + + expect($mdTheming.THEMES.hasOwnProperty('test')).toBeTruthy(); + })); + it('supports changing browser color on the fly', function() { var name = 'theme-color'; var primaryPalette = $mdThemingProvider._THEMES.default.colors.primary.name; @@ -758,7 +769,7 @@ describe('$mdTheming service', function() { describe('md-theme directive', function() { beforeEach(module('material.core')); - it('should observe and set mdTheme controller', inject(function($compile, $rootScope) { + it('should watch and set mdTheme controller', inject(function($compile, $rootScope) { $rootScope.themey = 'red'; var el = $compile('
')($rootScope); $rootScope.$apply(); @@ -768,23 +779,48 @@ describe('md-theme directive', function() { expect(ctrl.$mdTheme).toBe('blue'); })); - it('warns when an unregistered theme is used', function() { - inject(function($log, $compile, $rootScope) { - spyOn($log, 'warn'); - $compile('
')($rootScope); - $rootScope.$apply(); - expect($log.warn).toHaveBeenCalled(); - }); - }); + it('warns when an unregistered theme is used', inject(function ($log, $compile, $rootScope) { + spyOn($log, 'warn'); + $compile('
')($rootScope); + $rootScope.$apply(); + expect($log.warn).toHaveBeenCalled(); + })); + + it('does not warn when a registered theme is used', inject(function($log, $compile, $rootScope) { + spyOn($log, 'warn'); + $compile('
')($rootScope); + $rootScope.$apply(); + expect($log.warn.calls.count()).toBe(0); + })); - it('does not warn when a registered theme is used', function() { - inject(function($log, $compile, $rootScope) { - spyOn($log, 'warn'); - $compile('
')($rootScope); + it('should accept string as a theme', inject(function($compile, $rootScope, $mdTheming) { + var el = $compile('
')($rootScope); + $rootScope.$apply(); + $mdTheming(el); + expect(el.hasClass('md-default-theme')).toBeFalsy(); + expect(el.hasClass('md-red-theme')).toBeTruthy(); + })); + + it('should accept $q promise as a theme', inject(function($compile, $rootScope, $q, $mdTheming) { + $rootScope.promise = $mdTheming.defineTheme('red', { primary: 'red' }); + var el = $compile('
')($rootScope); + $mdTheming(el); + $rootScope.$apply(); + expect(el.hasClass('md-default-theme')).toBeFalsy(); + expect(el.hasClass('md-red-theme')).toBeTruthy(); + })); + + it('should accept a function that returns a promise as a theme', + inject(function ($compile, $rootScope, $q, $mdTheming) { + $rootScope.func = function () { + return $mdTheming.defineTheme('red', {primary: 'red'}); + }; + var el = $compile('
')($rootScope); + $mdTheming(el); $rootScope.$apply(); - expect($log.warn.calls.count()).toBe(0); - }); - }); + expect(el.hasClass('md-default-theme')).toBeFalsy(); + expect(el.hasClass('md-red-theme')).toBeTruthy(); + })); }); describe('md-themable directive', function() {