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

feat(dialog themes): extended theme inheritance of dialogs #9762

Merged
merged 1 commit into from
Oct 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/components/dialog/demoThemeInheritance/dialog1.tmpl.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<md-dialog aria-label="Mango (Fruit)">
<form ng-cloak>
<md-toolbar>
<div class="md-toolbar-tools">
<h2>Mango (Fruit)</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="cancel()">
<md-icon md-svg-src="img/icons/ic_close_24px.svg" aria-label="Close dialog"></md-icon>
</md-button>
</div>
</md-toolbar>

<md-dialog-content>
<div class="md-dialog-content">
<h2>Using .md-dialog-content class that sets the padding as the spec</h2>
<p>
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.
</p>

<img style="margin: auto; max-width: 100%;" alt="Lush mango tree" src="img/mangues.jpg">

<p>
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&mdash;the "common mango" or "Indian mango"&mdash;is the only mango tree commonly cultivated in many tropical and subtropical regions.
</p>
<p>
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.
</p>
</div>
</md-dialog-content>

<md-dialog-actions layout="row">
<md-button href="http://en.wikipedia.org/wiki/Mango" target="_blank" md-autofocus>
More on Wikipedia
</md-button>
<span flex></span>
<md-button ng-click="answer('not useful')">
Not Useful
</md-button>
<md-button ng-click="answer('useful')" class="md-primary">
Useful
</md-button>
</md-dialog-actions>
</form>
</md-dialog>
7 changes: 7 additions & 0 deletions src/components/dialog/demoThemeInheritance/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div ng-controller="AppCtrl" ng-cloak md-theme="{{theme}}" class="container">
<p class="inset">
We have an interval that changes the color of the button from <code>red</code> to <code>blue</code> themes,
Open the dialog to see the theme being inherited to the dialog and any component inside
</p>
<md-button class="md-primary md-raised" ng-click="showAdvanced($event)">Open Dialog</md-button>
</div>
49 changes: 49 additions & 0 deletions src/components/dialog/demoThemeInheritance/script.js
Original file line number Diff line number Diff line change
@@ -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);
};
}
});
3 changes: 3 additions & 0 deletions src/components/dialog/demoThemeInheritance/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.container {
text-align: center;
}
42 changes: 31 additions & 11 deletions src/components/dialog/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ function MdDialogProvider($$interimElementProvider) {
function advancedDialogOptions($mdDialog, $mdConstant) {
return {
template: [
'<md-dialog md-theme="{{ dialog.theme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
'<md-dialog md-theme="{{ dialog.theme || dialog.defaultTheme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
' <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',
' <h2 class="md-title">{{ dialog.title }}</h2>',
' <div ng-if="::dialog.mdHtmlContent" class="md-dialog-content-body" ',
Expand Down Expand Up @@ -638,7 +638,7 @@ function MdDialogProvider($$interimElementProvider) {

/* @ngInject */
function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
$log, $injector, $mdTheming) {
$log, $injector, $mdTheming, $interpolate) {

return {
hasBackdrop: true,
Expand All @@ -661,7 +661,10 @@ function MdDialogProvider($$interimElementProvider) {
// an element outside of the container, and the focus trap won't work probably..
// Also the tabindex is needed for the `escapeToClose` functionality, because
// the keyDown event can't be triggered when the focus is outside of the container.
return '<div class="md-dialog-container" tabindex="-1">' + validatedTemplate(template) + '</div>';
var startSymbol = $interpolate.startSymbol();
var endSymbol = $interpolate.endSymbol();
var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol;
return '<div class="md-dialog-container" tabindex="-1" md-theme="' + theme + '">' + validatedTemplate(template) + '</div>';

/**
* The specified template should contain a <md-dialog> wrapper element....
Expand All @@ -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);
}

Expand Down Expand Up @@ -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();
}
});
}

/**
Expand Down
79 changes: 79 additions & 0 deletions src/components/dialog/dialog.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<div id="rawContent">Hello</div>';
var parent = angular.element('<div>');

var button = $compile('<button ng-click="showDialog($event)" md-theme="myTheme">test</button>')($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 = '<div id="rawContent">Hello</div>';
var parent = angular.element('<div>');

$rootScope.theme = 'myTheme';

var button = $compile('<button ng-click="showDialog($event)" md-theme="{{theme}}">test</button>')($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 = '<div id="rawContent">Hello</div>';
var parent = angular.element('<div>');

$rootScope.theme = function () {
return 'myTheme';
};

var button = $compile('<button ng-click="showDialog($event)" md-theme="theme">test</button>')($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) {
Expand Down
66 changes: 50 additions & 16 deletions src/core/services/theming/theming.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; };
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
}
}
});
}
}
};
Expand Down
Loading