diff --git a/src/components/dialog/demoThemeInheritance/dialog1.tmpl.html b/src/components/dialog/demoThemeInheritance/dialog1.tmpl.html new file mode 100644 index 00000000000..4e7ff32ae31 --- /dev/null +++ b/src/components/dialog/demoThemeInheritance/dialog1.tmpl.html @@ -0,0 +1,44 @@ + +
+ +
+

Mango (Fruit)

+ + + + +
+
+ + +
+

Using .md-dialog-content class that sets the padding as the spec

+

+ 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. +

+ + Lush mango tree + +

+ 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—the "common mango" or "Indian mango"—is the only mango tree commonly cultivated in many tropical and subtropical regions. +

+

+ 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. +

+
+
+ + + + More on Wikipedia + + + + Not Useful + + + Useful + + +
+
diff --git a/src/components/dialog/demoThemeInheritance/index.html b/src/components/dialog/demoThemeInheritance/index.html new file mode 100644 index 00000000000..23d4eafdc12 --- /dev/null +++ b/src/components/dialog/demoThemeInheritance/index.html @@ -0,0 +1,7 @@ +
+

+ We have an interval that changes the color of the button from red to blue themes, + Open the dialog to see the theme being inherited to the dialog and any component inside +

+ Open Dialog +
diff --git a/src/components/dialog/demoThemeInheritance/script.js b/src/components/dialog/demoThemeInheritance/script.js new file mode 100644 index 00000000000..5cabec2eb73 --- /dev/null +++ b/src/components/dialog/demoThemeInheritance/script.js @@ -0,0 +1,49 @@ +angular.module('dialogDemo1', ['ngMaterial']) + .config(function ($mdThemingProvider) { + $mdThemingProvider.theme('red') + .primaryPalette('red'); + + $mdThemingProvider.theme('blue') + .primaryPalette('blue'); + + }) +.controller('AppCtrl', function($scope, $mdDialog, $interval) { + $scope.theme = 'red'; + + var isThemeRed = true; + + $interval(function () { + $scope.theme = isThemeRed ? 'blue' : 'red'; + + isThemeRed = !isThemeRed; + }, 2000); + + $scope.showAdvanced = function(ev) { + $mdDialog.show({ + controller: DialogController, + templateUrl: 'dialog1.tmpl.html', + parent: angular.element(document.body), + targetEvent: ev, + clickOutsideToClose:true + }) + .then(function(answer) { + $scope.status = 'You said the information was "' + answer + '".'; + }, function() { + $scope.status = 'You cancelled the dialog.'; + }); + }; + + function DialogController($scope, $mdDialog) { + $scope.hide = function() { + $mdDialog.hide(); + }; + + $scope.cancel = function() { + $mdDialog.cancel(); + }; + + $scope.answer = function(answer) { + $mdDialog.hide(answer); + }; + } +}); diff --git a/src/components/dialog/demoThemeInheritance/style.css b/src/components/dialog/demoThemeInheritance/style.css new file mode 100644 index 00000000000..d2a4fa1591f --- /dev/null +++ b/src/components/dialog/demoThemeInheritance/style.css @@ -0,0 +1,3 @@ +.container { + text-align: center; +} diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index 2a30733c159..715ce47be1b 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -588,7 +588,7 @@ function MdDialogProvider($$interimElementProvider) { function advancedDialogOptions($mdDialog, $mdConstant) { return { template: [ - '', + '', ' ', '

{{ dialog.title }}

', '
' + validatedTemplate(template) + '
'; + var startSymbol = $interpolate.startSymbol(); + var endSymbol = $interpolate.endSymbol(); + var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol; + return '
' + validatedTemplate(template) + '
'; /** * The specified template should contain a wrapper element.... @@ -680,6 +683,8 @@ function MdDialogProvider($$interimElementProvider) { // Automatically apply the theme, if the user didn't specify a theme explicitly. // Those option changes need to be done, before the compilation has started, because otherwise // the option changes will be not available in the $mdCompilers locales. + options.defaultTheme = $mdTheming.defaultTheme(); + detectTheming(options); } @@ -798,19 +803,34 @@ function MdDialogProvider($$interimElementProvider) { } function detectTheming(options) { - // Only detect the theming, if the developer didn't specify the theme specifically. - if (options.theme) return; + // Once the user specifies a targetEvent, we will automatically try to find the correct + // nested theme. + var targetEl; + if (options.targetEvent && options.targetEvent.target) { + targetEl = angular.element(options.targetEvent.target); + } - options.theme = $mdTheming.defaultTheme(); + var themeCtrl = targetEl && targetEl.controller('mdTheme'); - if (options.targetEvent && options.targetEvent.target) { - var targetEl = angular.element(options.targetEvent.target); + if (!themeCtrl) { + return; + } - // Once the user specifies a targetEvent, we will automatically try to find the correct - // nested theme. - options.theme = (targetEl.controller('mdTheme') || {}).$mdTheme || options.theme; + options.themeWatch = themeCtrl.$shouldWatch; + + var theme = options.theme || themeCtrl.$mdTheme; + + if (theme) { + options.scope.theme = theme; } + var unwatch = themeCtrl.registerChanges(function (newTheme) { + options.scope.theme = newTheme; + + if (!options.themeWatch) { + unwatch(); + } + }); } /** diff --git a/src/components/dialog/dialog.spec.js b/src/components/dialog/dialog.spec.js index 288053ccbcc..68eaddee513 100644 --- a/src/components/dialog/dialog.spec.js +++ b/src/components/dialog/dialog.spec.js @@ -1657,6 +1657,85 @@ describe('$mdDialog', function() { // Clean up our modifications to the DOM. document.body.removeChild(parent); }); + + describe('theming', function () { + it('should inherit targetElement theme', inject(function($mdDialog, $mdTheming, $rootScope, $compile) { + var template = '
Hello
'; + var parent = angular.element('
'); + + var button = $compile('')($rootScope); + + $mdTheming(button); + + $rootScope.showDialog = function (ev) { + $mdDialog.show({ + template: template, + parent: parent, + targetEvent: ev + }); + }; + + button[0].click(); + + var container = parent[0].querySelector('.md-dialog-container'); + var dialog = angular.element(container).find('md-dialog'); + expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy(); + })); + + it('should watch targetElement theme if it has interpolation', inject(function($mdDialog, $mdTheming, $rootScope, $compile) { + var template = '
Hello
'; + var parent = angular.element('
'); + + $rootScope.theme = 'myTheme'; + + var button = $compile('')($rootScope); + + $mdTheming(button); + + $rootScope.showDialog = function (ev) { + $mdDialog.show({ + template: template, + parent: parent, + targetEvent: ev + }); + }; + + button[0].click(); + + var container = parent[0].querySelector('.md-dialog-container'); + var dialog = angular.element(container).find('md-dialog'); + expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy(); + $rootScope.$apply('theme = "anotherTheme"'); + expect(dialog.hasClass('md-anotherTheme-theme')).toBeTruthy(); + })); + + it('should resolve targetElement theme if it\'s a function', inject(function($mdDialog, $mdTheming, $rootScope, $compile) { + var template = '
Hello
'; + var parent = angular.element('
'); + + $rootScope.theme = function () { + return 'myTheme'; + }; + + var button = $compile('')($rootScope); + + $mdTheming(button); + + $rootScope.showDialog = function (ev) { + $mdDialog.show({ + template: template, + parent: parent, + targetEvent: ev + }); + }; + + button[0].click(); + + var container = parent[0].querySelector('.md-dialog-container'); + var dialog = angular.element(container).find('md-dialog'); + expect(dialog.hasClass('md-myTheme-theme')).toBeTruthy(); + })); + }); }); function hasConfigurationMethods(preset, methods) { diff --git a/src/core/services/theming/theming.js b/src/core/services/theming/theming.js index 7a9de4887bd..cd562b996be 100644 --- a/src/core/services/theming/theming.js +++ b/src/core/services/theming/theming.js @@ -624,6 +624,11 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { return angular.extend({}, PALETTES); } }); + Object.defineProperty(applyTheme, 'ALWAYS_WATCH', { + get: function () { + return alwaysWatchTheme; + } + }); applyTheme.inherit = inheritTheme; applyTheme.registered = registered; applyTheme.defaultTheme = function() { return defaultTheme; }; @@ -674,8 +679,9 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { updateThemeClass(lookupThemeName()); if (ctrl) { - var watchTheme = - alwaysWatchTheme || ctrl.$shouldWatch || $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')); + var watchTheme = alwaysWatchTheme || + ctrl.$shouldWatch || + $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')); var unwatch = ctrl.registerChanges(function (name) { updateThemeClass(name); @@ -723,10 +729,27 @@ function ThemingProvider($mdColorPalette, $$mdMetaProvider) { function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) { return { - priority: 100, + priority: 101, // has to be more than 100 to be before interpolation (issue on IE) link: { pre: function(scope, el, attrs) { var registeredCallbacks = []; + + var startSymbol = $interpolate.startSymbol(); + var endSymbol = $interpolate.endSymbol(); + + var theme = attrs.mdTheme.trim(); + + var hasInterpolation = + theme.substr(0, startSymbol.length) === startSymbol && + theme.lastIndexOf(endSymbol) === theme.length - endSymbol.length; + + var oneTimeOperator = '::'; + var oneTimeBind = attrs.mdTheme + .split(startSymbol).join('') + .split(endSymbol).join('') + .trim() + .substr(0, oneTimeOperator.length) === oneTimeOperator; + var ctrl = { registerChanges: function (cb, context) { if (context) { @@ -748,38 +771,49 @@ function ThemingDirective($mdTheming, $interpolate, $parse, $mdUtil, $q, $log) { $log.warn('attempted to use unregistered theme \'' + theme + '\''); } - ctrl.$mdTheme = theme; // Iterating backwards to support unregistering during iteration // http://stackoverflow.com/a/9882349/890293 - registeredCallbacks.reverse().forEach(function (cb) { - cb(theme); - }); + // we don't use `reverse()` of array because it mutates the array and we don't want it to get re-indexed + for (var i = registeredCallbacks.length; i--;) { + registeredCallbacks[i](theme); + } }, - $shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) + $shouldWatch: $mdUtil.parseAttributeBoolean(el.attr('md-theme-watch')) || + $mdTheming.ALWAYS_WATCH || + (hasInterpolation && !oneTimeBind) }; el.data('$mdThemeController', ctrl); - var getThemeInterpolation = function () { return $interpolate(attrs.mdTheme)(scope); }; - - var setParsedTheme = function (interpolation) { - var theme = $parse(interpolation)(scope) || interpolation; + var getTheme = function () { + var interpolation = $interpolate(attrs.mdTheme)(scope); + return $parse(interpolation)(scope) || interpolation; + }; + var setParsedTheme = function (theme) { if (typeof theme === 'string') { return ctrl.$setTheme(theme); } - $q.when( (typeof theme === 'function') ? theme() : theme ) + $q.when( angular.isFunction(theme) ? theme() : theme ) .then(function(name){ - ctrl.$setTheme(name) + ctrl.$setTheme(name); }); }; - setParsedTheme(getThemeInterpolation()); + setParsedTheme(getTheme()); - scope.$watch(getThemeInterpolation, setParsedTheme); + var unwatch = scope.$watch(getTheme, function(theme) { + if (theme) { + setParsedTheme(theme); + + if (!ctrl.$shouldWatch) { + unwatch(); + } + } + }); } } }; diff --git a/src/core/services/theming/theming.spec.js b/src/core/services/theming/theming.spec.js index a29b99ee6da..047da92a631 100644 --- a/src/core/services/theming/theming.spec.js +++ b/src/core/services/theming/theming.spec.js @@ -769,15 +769,17 @@ describe('$mdTheming service', function() { describe('md-theme directive', function() { beforeEach(module('material.core')); - it('should watch and set mdTheme controller', inject(function($compile, $rootScope) { - $rootScope.themey = 'red'; - var el = $compile('
')($rootScope); - $rootScope.$apply(); - var ctrl = el.data('$mdThemeController'); - expect(ctrl.$mdTheme).toBe('red'); - $rootScope.$apply('themey = "blue"'); - expect(ctrl.$mdTheme).toBe('blue'); - })); + it('should watch and set mdTheme controller', + inject(function ($compile, $rootScope) { + $rootScope.themey = 'red'; + var el = $compile('
')($rootScope); + $rootScope.$apply(); + var ctrl = el.data('$mdThemeController'); + expect(ctrl.$mdTheme).toBe('red'); + $rootScope.$apply('themey = "blue"'); + expect(ctrl.$mdTheme).toBe('blue'); + }) + ); it('warns when an unregistered theme is used', inject(function ($log, $compile, $rootScope) { spyOn($log, 'warn'); @@ -801,6 +803,31 @@ describe('md-theme directive', function() { expect(el.hasClass('md-red-theme')).toBeTruthy(); })); + it('should accept interpolation string as a theme and automatically watch changes', + inject(function ($compile, $rootScope, $mdTheming) { + $rootScope.themey = 'red'; + var el = $compile('
')($rootScope); + $mdTheming(el); + $rootScope.$apply(); + expect(el.hasClass('md-red-theme')).toBeTruthy(); + $rootScope.$apply('themey = "blue"'); + expect(el.hasClass('md-blue-theme')).toBeTruthy(); + }) + ); + + it('should accept onetime bind interpolation string as a theme and not watch changes', + inject(function ($compile, $rootScope, $mdTheming) { + $rootScope.themey = 'red'; + var el = $compile('
')($rootScope); + $mdTheming(el); + $rootScope.$apply(); + expect(el.hasClass('md-red-theme')).toBeTruthy(); + $rootScope.$apply('themey = "blue"'); + expect(el.hasClass('md-blue-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); @@ -821,6 +848,40 @@ describe('md-theme directive', function() { expect(el.hasClass('md-default-theme')).toBeFalsy(); expect(el.hasClass('md-red-theme')).toBeTruthy(); })); + + describe('$shouldWatch controller property', function () { + it('should set to true if there\'s a md-theme-watch attribute', + inject(function ($mdTheming, $compile, $rootScope) { + var el = $compile('
')($rootScope); + $mdTheming(el); + $rootScope.$apply(); + + expect(el.controller('mdTheme').$shouldWatch).toBeTruthy(); + }) + ); + + it('should set to true if there\'s an interpolation', + inject(function ($mdTheming, $compile, $rootScope) { + $rootScope.theme = 'default'; + var el = $compile('
')($rootScope); + $mdTheming(el); + $rootScope.$apply(); + + expect(el.controller('mdTheme').$shouldWatch).toBeTruthy(); + }) + ); + + it('should set to false if there\'s an interpolation with one way binding', + inject(function ($mdTheming, $compile, $rootScope) { + $rootScope.theme = 'default'; + var el = $compile('
')($rootScope); + $mdTheming(el); + $rootScope.$apply(); + + expect(el.controller('mdTheme').$shouldWatch).toBeFalsy(); + }) + ); + }); }); describe('md-themable directive', function() { @@ -846,30 +907,17 @@ describe('md-themable directive', function() { expect(el.children().hasClass('md-red-theme')).toBe(false); })); - it('should not watch parent theme by default', inject(function($compile, $rootScope) { + it('should watch parent theme by default', inject(function($compile, $rootScope) { $rootScope.themey = 'red'; var el = $compile('
')($rootScope); $rootScope.$apply(); expect(el.children().hasClass('md-red-theme')).toBe(true); $rootScope.$apply('themey = "blue"'); - expect(el.children().hasClass('md-blue-theme')).toBe(false); - expect(el.children().hasClass('md-red-theme')).toBe(true); + expect(el.children().hasClass('md-blue-theme')).toBe(true); + expect(el.children().hasClass('md-red-theme')).toBe(false); })); - it('should support watching parent theme by default', function() { - $mdThemingProvider.alwaysWatchTheme(true); - inject(function($rootScope, $compile) { - $rootScope.themey = 'red'; - var el = $compile('
')($rootScope); - $rootScope.$apply(); - expect(el.children().hasClass('md-red-theme')).toBe(true); - $rootScope.$apply('themey = "blue"'); - expect(el.children().hasClass('md-blue-theme')).toBe(false); - expect(el.children().hasClass('md-red-theme')).toBe(true); - }); - }); - it('should not apply a class for an unnested default theme', inject(function($rootScope, $compile) { var el = $compile('
')($rootScope); expect(el.hasClass('md-default-theme')).toBe(false);