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

Commit 57f2afd

Browse files
EladBezaleljelbourn
authored andcommitted
feat(browser-color): enable browser header coloring (#9192)
- provides an easy way to set and change the browser color according the material design spec colors and the defined themes fixes #8062
1 parent 195966f commit 57f2afd

File tree

5 files changed

+388
-2
lines changed

5 files changed

+388
-2
lines changed

docs/app/js/app.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ function(SERVICES, COMPONENTS, DEMOS, PAGES,
6868
.primaryPalette('docs-blue')
6969
.accentPalette('docs-red');
7070

71+
$mdThemingProvider
72+
.enableBrowserColor();
73+
7174
angular.forEach(PAGES, function(pages, area) {
7275
angular.forEach(pages, function(page) {
7376
$routeProvider

src/core/services/meta/meta.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* @ngdoc service
3+
* @name $$mdMeta
4+
* @module material.core.meta
5+
*
6+
* @description
7+
*
8+
* A provider and a service that simplifies meta tags access
9+
*
10+
* Note: This is intended only for use with dynamic meta tags such as browser color and title.
11+
* Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)
12+
* will not work since `$$mdMeta` adds the tags after the page has already been loaded.
13+
*
14+
* ```js
15+
* app.config(function($$mdMetaProvider) {
16+
* var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');
17+
* var metaValue = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'
18+
*
19+
* removeMeta();
20+
* });
21+
*
22+
* app.controller('myController', function($$mdMeta) {
23+
* var removeMeta = $$mdMeta.setMeta('meta-name', 'content');
24+
* var metaValue = $$mdMeta.getMeta('meta-name'); // -> 'content'
25+
*
26+
* removeMeta();
27+
* });
28+
* ```
29+
*
30+
* @returns {$$mdMeta.$service}
31+
*
32+
*/
33+
angular.module('material.core.meta', [])
34+
.provider('$$mdMeta', function () {
35+
var head = angular.element(document.head);
36+
var metaElements = {};
37+
38+
/**
39+
* Checks if the requested element was written manually and maps it
40+
*
41+
* @param {string} name meta tag 'name' attribute value
42+
* @returns {boolean} returns true if there is an element with the requested name
43+
*/
44+
function mapExistingElement(name) {
45+
if (metaElements[name]) {
46+
return true;
47+
}
48+
49+
var element = document.getElementsByName(name)[0];
50+
51+
if (!element) {
52+
return false;
53+
}
54+
55+
metaElements[name] = angular.element(element);
56+
57+
return true;
58+
}
59+
60+
/**
61+
* @ngdoc method
62+
* @name $$mdMeta#setMeta
63+
*
64+
* @description
65+
* Creates meta element with the 'name' and 'content' attributes,
66+
* if the meta tag is already created than we replace the 'content' value
67+
*
68+
* @param {string} name meta tag 'name' attribute value
69+
* @param {string} content meta tag 'content' attribute value
70+
* @returns {function} remove function
71+
*
72+
*/
73+
function setMeta(name, content) {
74+
mapExistingElement(name);
75+
76+
if (!metaElements[name]) {
77+
var newMeta = angular.element('<meta name="' + name + '" content="' + content + '"/>');
78+
head.append(newMeta);
79+
metaElements[name] = newMeta;
80+
}
81+
else {
82+
metaElements[name].attr('content', content);
83+
}
84+
85+
return function () {
86+
metaElements[name].attr('content', '');
87+
metaElements[name].remove();
88+
delete metaElements[name];
89+
};
90+
}
91+
92+
/**
93+
* @ngdoc method
94+
* @name $$mdMeta#getMeta
95+
*
96+
* @description
97+
* Gets the 'content' attribute value of the wanted meta element
98+
*
99+
* @param {string} name meta tag 'name' attribute value
100+
* @returns {string} content attribute value
101+
*/
102+
function getMeta(name) {
103+
if (!mapExistingElement(name)) {
104+
throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
105+
}
106+
107+
return metaElements[name].attr('content');
108+
}
109+
110+
var module = {
111+
setMeta: setMeta,
112+
getMeta: getMeta
113+
};
114+
115+
return angular.extend({}, module, {
116+
$get: function () {
117+
return module;
118+
}
119+
});
120+
});

src/core/services/meta/meta.spec.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
describe('$$mdMeta', function() {
2+
var $$mdMeta;
3+
4+
beforeEach(module('material.core'));
5+
6+
beforeEach(function() {
7+
inject(function(_$$mdMeta_) {
8+
$$mdMeta = _$$mdMeta_;
9+
});
10+
});
11+
12+
describe('set meta', function () {
13+
beforeEach(function () {
14+
angular.element(document.getElementsByTagName('meta')).remove();
15+
});
16+
17+
it('should create the element and append it to the body', function() {
18+
var name = 'test';
19+
var content = 'value';
20+
21+
expect(document.getElementsByName(name).length).toEqual(0);
22+
23+
$$mdMeta.setMeta(name, content);
24+
25+
expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content);
26+
});
27+
28+
it('should update the existing meta tag', function() {
29+
var name = 'test';
30+
var content = 'value';
31+
32+
$$mdMeta.setMeta(name, content);
33+
34+
expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content);
35+
36+
$$mdMeta.setMeta(name, content + '2');
37+
38+
expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content + '2');
39+
});
40+
41+
it('should map existing meta tag', function() {
42+
var name = 'test';
43+
var content = 'value';
44+
45+
var element = angular.element('<meta name="' + name + '" content="' + content + '"/>');
46+
angular.element(document.head).append(element);
47+
48+
expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content);
49+
50+
$$mdMeta.setMeta(name, content + '2');
51+
52+
expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content + '2');
53+
});
54+
55+
it('should return a remove function', function() {
56+
var name = 'test';
57+
var content = 'value';
58+
59+
var remove = $$mdMeta.setMeta(name, content);
60+
61+
expect(document.getElementsByName(name).length).toBe(1);
62+
63+
remove();
64+
65+
expect(document.getElementsByName(name).length).toBe(0);
66+
67+
});
68+
});
69+
70+
describe('get meta', function () {
71+
beforeEach(function () {
72+
angular.element(document.getElementsByTagName('meta')).remove();
73+
});
74+
75+
it('should return the meta content', function() {
76+
var name = 'test';
77+
var content = 'value';
78+
79+
$$mdMeta.setMeta(name, content);
80+
81+
expect($$mdMeta.getMeta(name)).toBe(content);
82+
});
83+
84+
it('should reject unavailable meta tags', function() {
85+
var name = 'test';
86+
87+
expect(function () {
88+
$$mdMeta.getMeta(name);
89+
}).toThrowError('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
90+
});
91+
92+
it('should add not mapped meta tag to the hashmap', function() {
93+
var name = 'test';
94+
var content = 'value';
95+
96+
var element = angular.element('<meta name="' + name + '" content="' + content + '"/>');
97+
angular.element(document.head).append(element);
98+
99+
expect($$mdMeta.getMeta(name)).toBe(content);
100+
});
101+
});
102+
});

src/core/services/theming/theming.js

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @description
77
* Theming
88
*/
9-
angular.module('material.core.theming', ['material.core.theming.palette'])
9+
angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])
1010
.directive('mdTheme', ThemingDirective)
1111
.directive('mdThemable', ThemableDirective)
1212
.directive('mdThemesDisabled', disableThemesDirective )
@@ -93,6 +93,29 @@ function detectDisabledThemes($mdThemingProvider) {
9393
* $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));
9494
* });
9595
* </hljs>
96+
*
97+
* ### Browser color
98+
*
99+
* Enables browser header coloring
100+
* for more info please visit:
101+
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
102+
*
103+
* Options parameter: <br/>
104+
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
105+
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
106+
* 'accent', 'background' and 'warn'. Default is `primary`. <br/>
107+
* `hue` - The hue from the selected palette. Default is `800`<br/>
108+
*
109+
* <hljs lang="js">
110+
* myAppModule.config(function($mdThemingProvider) {
111+
* // Enable browser color
112+
* $mdThemingProvider.enableBrowserColor({
113+
* theme: 'myTheme', // Default is 'default'
114+
* palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available
115+
* hue: '200' // Default is '800'
116+
* });
117+
* });
118+
* </hljs>
96119
*/
97120

98121
/**
@@ -120,6 +143,17 @@ function detectDisabledThemes($mdThemingProvider) {
120143
* classes when they change. Default is `false`. Enabling can reduce performance.
121144
*/
122145

146+
/**
147+
* @ngdoc method
148+
* @name $mdThemingProvider#enableBrowserColor
149+
* @param {Object=} options Options object for the browser color<br/>
150+
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
151+
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
152+
* 'accent', 'background' and 'warn'. Default is `primary`. <br/>
153+
* `hue` - The hue from the selected palette. Default is `800`<br/>
154+
* @returns {Function} remove function of the browser color
155+
*/
156+
123157
/* Some Example Valid Theming Expressions
124158
* =======================================
125159
*
@@ -228,7 +262,7 @@ var themeConfig = {
228262
/**
229263
*
230264
*/
231-
function ThemingProvider($mdColorPalette) {
265+
function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
232266
PALETTES = { };
233267
var THEMES = { };
234268

@@ -242,6 +276,53 @@ function ThemingProvider($mdColorPalette) {
242276

243277
// Default theme defined in core.js
244278

279+
/**
280+
* Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter
281+
* @param {string} color Hex value of the wanted browser color
282+
* @returns {Function} Remove function of the meta tags
283+
*/
284+
var setBrowserColor = function (color) {
285+
// Chrome, Firefox OS and Opera
286+
var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);
287+
// Windows Phone
288+
var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);
289+
290+
return function () {
291+
removeChrome();
292+
removeWindows();
293+
}
294+
};
295+
296+
/**
297+
* Enables browser header coloring
298+
* for more info please visit:
299+
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
300+
*
301+
* The default color is `800` from `primary` palette of the `default` theme
302+
*
303+
* options are:
304+
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme
305+
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
306+
* 'accent', 'background' and 'warn'. Default is `primary`
307+
* `hue` - The hue from the selected palette. Default is `800`
308+
*
309+
* @param {Object=} options Options object for the browser color
310+
* @returns {Function} remove function of the browser color
311+
*/
312+
var enableBrowserColor = function (options) {
313+
options = angular.isObject(options) ? options : {};
314+
315+
var theme = options.theme || 'default';
316+
var hue = options.hue || '800';
317+
318+
var palette = PALETTES[options.palette] ||
319+
PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];
320+
321+
var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];
322+
323+
return setBrowserColor(color);
324+
};
325+
245326
return themingProvider = {
246327
definePalette: definePalette,
247328
extendPalette: extendPalette,
@@ -282,6 +363,8 @@ function ThemingProvider($mdColorPalette) {
282363
alwaysWatchTheme = alwaysWatch;
283364
},
284365

366+
enableBrowserColor: enableBrowserColor,
367+
285368
$get: ThemingService,
286369
_LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
287370
_DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
@@ -464,6 +547,7 @@ function ThemingProvider($mdColorPalette) {
464547
applyTheme.registered = registered;
465548
applyTheme.defaultTheme = function() { return defaultTheme; };
466549
applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
550+
applyTheme.setBrowserColor = enableBrowserColor;
467551

468552
return applyTheme;
469553

@@ -769,6 +853,7 @@ function generateAllThemes($injector, $mdTheming) {
769853
}
770854

771855
palette[hueName] = {
856+
hex: palette[hueName],
772857
value: rgbValue,
773858
contrast: getContrastColor()
774859
};

0 commit comments

Comments
 (0)