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

Commit

Permalink
feat(browser-color): enable browser header coloring (#9192)
Browse files Browse the repository at this point in the history
- provides an easy way to set and change the browser color according the material design spec colors and the defined themes

fixes #8062
  • Loading branch information
EladBezalel authored and jelbourn committed Aug 23, 2016
1 parent 195966f commit 57f2afd
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/app/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ function(SERVICES, COMPONENTS, DEMOS, PAGES,
.primaryPalette('docs-blue')
.accentPalette('docs-red');

$mdThemingProvider
.enableBrowserColor();

angular.forEach(PAGES, function(pages, area) {
angular.forEach(pages, function(page) {
$routeProvider
Expand Down
120 changes: 120 additions & 0 deletions src/core/services/meta/meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* @ngdoc service
* @name $$mdMeta
* @module material.core.meta
*
* @description
*
* A provider and a service that simplifies meta tags access
*
* Note: This is intended only for use with dynamic meta tags such as browser color and title.
* Tags that are only processed when the page is rendered (such as `charset`, and `http-equiv`)
* will not work since `$$mdMeta` adds the tags after the page has already been loaded.
*
* ```js
* app.config(function($$mdMetaProvider) {
* var removeMeta = $$mdMetaProvider.setMeta('meta-name', 'content');
* var metaValue = $$mdMetaProvider.getMeta('meta-name'); // -> 'content'
*
* removeMeta();
* });
*
* app.controller('myController', function($$mdMeta) {
* var removeMeta = $$mdMeta.setMeta('meta-name', 'content');
* var metaValue = $$mdMeta.getMeta('meta-name'); // -> 'content'
*
* removeMeta();
* });
* ```
*
* @returns {$$mdMeta.$service}
*
*/
angular.module('material.core.meta', [])
.provider('$$mdMeta', function () {
var head = angular.element(document.head);
var metaElements = {};

/**
* Checks if the requested element was written manually and maps it
*
* @param {string} name meta tag 'name' attribute value
* @returns {boolean} returns true if there is an element with the requested name
*/
function mapExistingElement(name) {
if (metaElements[name]) {
return true;
}

var element = document.getElementsByName(name)[0];

if (!element) {
return false;
}

metaElements[name] = angular.element(element);

return true;
}

/**
* @ngdoc method
* @name $$mdMeta#setMeta
*
* @description
* Creates meta element with the 'name' and 'content' attributes,
* if the meta tag is already created than we replace the 'content' value
*
* @param {string} name meta tag 'name' attribute value
* @param {string} content meta tag 'content' attribute value
* @returns {function} remove function
*
*/
function setMeta(name, content) {
mapExistingElement(name);

if (!metaElements[name]) {
var newMeta = angular.element('<meta name="' + name + '" content="' + content + '"/>');
head.append(newMeta);
metaElements[name] = newMeta;
}
else {
metaElements[name].attr('content', content);
}

return function () {
metaElements[name].attr('content', '');
metaElements[name].remove();
delete metaElements[name];
};
}

/**
* @ngdoc method
* @name $$mdMeta#getMeta
*
* @description
* Gets the 'content' attribute value of the wanted meta element
*
* @param {string} name meta tag 'name' attribute value
* @returns {string} content attribute value
*/
function getMeta(name) {
if (!mapExistingElement(name)) {
throw Error('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
}

return metaElements[name].attr('content');
}

var module = {
setMeta: setMeta,
getMeta: getMeta
};

return angular.extend({}, module, {
$get: function () {
return module;
}
});
});
102 changes: 102 additions & 0 deletions src/core/services/meta/meta.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
describe('$$mdMeta', function() {
var $$mdMeta;

beforeEach(module('material.core'));

beforeEach(function() {
inject(function(_$$mdMeta_) {
$$mdMeta = _$$mdMeta_;
});
});

describe('set meta', function () {
beforeEach(function () {
angular.element(document.getElementsByTagName('meta')).remove();
});

it('should create the element and append it to the body', function() {
var name = 'test';
var content = 'value';

expect(document.getElementsByName(name).length).toEqual(0);

$$mdMeta.setMeta(name, content);

expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content);
});

it('should update the existing meta tag', function() {
var name = 'test';
var content = 'value';

$$mdMeta.setMeta(name, content);

expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content);

$$mdMeta.setMeta(name, content + '2');

expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content + '2');
});

it('should map existing meta tag', function() {
var name = 'test';
var content = 'value';

var element = angular.element('<meta name="' + name + '" content="' + content + '"/>');
angular.element(document.head).append(element);

expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content);

$$mdMeta.setMeta(name, content + '2');

expect(angular.element(document.getElementsByName(name)[0]).attr('content')).toBe(content + '2');
});

it('should return a remove function', function() {
var name = 'test';
var content = 'value';

var remove = $$mdMeta.setMeta(name, content);

expect(document.getElementsByName(name).length).toBe(1);

remove();

expect(document.getElementsByName(name).length).toBe(0);

});
});

describe('get meta', function () {
beforeEach(function () {
angular.element(document.getElementsByTagName('meta')).remove();
});

it('should return the meta content', function() {
var name = 'test';
var content = 'value';

$$mdMeta.setMeta(name, content);

expect($$mdMeta.getMeta(name)).toBe(content);
});

it('should reject unavailable meta tags', function() {
var name = 'test';

expect(function () {
$$mdMeta.getMeta(name);
}).toThrowError('$$mdMeta: could not find a meta tag with the name \'' + name + '\'');
});

it('should add not mapped meta tag to the hashmap', function() {
var name = 'test';
var content = 'value';

var element = angular.element('<meta name="' + name + '" content="' + content + '"/>');
angular.element(document.head).append(element);

expect($$mdMeta.getMeta(name)).toBe(content);
});
});
});
89 changes: 87 additions & 2 deletions src/core/services/theming/theming.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @description
* Theming
*/
angular.module('material.core.theming', ['material.core.theming.palette'])
angular.module('material.core.theming', ['material.core.theming.palette', 'material.core.meta'])
.directive('mdTheme', ThemingDirective)
.directive('mdThemable', ThemableDirective)
.directive('mdThemesDisabled', disableThemesDirective )
Expand Down Expand Up @@ -93,6 +93,29 @@ function detectDisabledThemes($mdThemingProvider) {
* $mdThemingProvider.registerStyles(require('../styles/my-component.theme.css'));
* });
* </hljs>
*
* ### Browser color
*
* Enables browser header coloring
* for more info please visit:
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
*
* Options parameter: <br/>
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`. <br/>
* `hue` - The hue from the selected palette. Default is `800`<br/>
*
* <hljs lang="js">
* myAppModule.config(function($mdThemingProvider) {
* // Enable browser color
* $mdThemingProvider.enableBrowserColor({
* theme: 'myTheme', // Default is 'default'
* palette: 'accent', // Default is 'primary', any basic material palette and extended palettes are available
* hue: '200' // Default is '800'
* });
* });
* </hljs>
*/

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

/**
* @ngdoc method
* @name $mdThemingProvider#enableBrowserColor
* @param {Object=} options Options object for the browser color<br/>
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme. <br/>
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`. <br/>
* `hue` - The hue from the selected palette. Default is `800`<br/>
* @returns {Function} remove function of the browser color
*/

/* Some Example Valid Theming Expressions
* =======================================
*
Expand Down Expand Up @@ -228,7 +262,7 @@ var themeConfig = {
/**
*
*/
function ThemingProvider($mdColorPalette) {
function ThemingProvider($mdColorPalette, $$mdMetaProvider) {
PALETTES = { };
var THEMES = { };

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

// Default theme defined in core.js

/**
* Adds `theme-color` and `msapplication-navbutton-color` meta tags with the color parameter
* @param {string} color Hex value of the wanted browser color
* @returns {Function} Remove function of the meta tags
*/
var setBrowserColor = function (color) {
// Chrome, Firefox OS and Opera
var removeChrome = $$mdMetaProvider.setMeta('theme-color', color);
// Windows Phone
var removeWindows = $$mdMetaProvider.setMeta('msapplication-navbutton-color', color);

return function () {
removeChrome();
removeWindows();
}
};

/**
* Enables browser header coloring
* for more info please visit:
* https://developers.google.com/web/fundamentals/design-and-ui/browser-customization/theme-color
*
* The default color is `800` from `primary` palette of the `default` theme
*
* options are:
* `theme` - A defined theme via `$mdThemeProvider` to use the palettes from. Default is `default` theme
* `palette` - Can be any one of the basic material design palettes, extended defined palettes and 'primary',
* 'accent', 'background' and 'warn'. Default is `primary`
* `hue` - The hue from the selected palette. Default is `800`
*
* @param {Object=} options Options object for the browser color
* @returns {Function} remove function of the browser color
*/
var enableBrowserColor = function (options) {
options = angular.isObject(options) ? options : {};

var theme = options.theme || 'default';
var hue = options.hue || '800';

var palette = PALETTES[options.palette] ||
PALETTES[THEMES[theme].colors[options.palette || 'primary'].name];

var color = angular.isObject(palette[hue]) ? palette[hue].hex : palette[hue];

return setBrowserColor(color);
};

return themingProvider = {
definePalette: definePalette,
extendPalette: extendPalette,
Expand Down Expand Up @@ -282,6 +363,8 @@ function ThemingProvider($mdColorPalette) {
alwaysWatchTheme = alwaysWatch;
},

enableBrowserColor: enableBrowserColor,

$get: ThemingService,
_LIGHT_DEFAULT_HUES: LIGHT_DEFAULT_HUES,
_DARK_DEFAULT_HUES: DARK_DEFAULT_HUES,
Expand Down Expand Up @@ -464,6 +547,7 @@ function ThemingProvider($mdColorPalette) {
applyTheme.registered = registered;
applyTheme.defaultTheme = function() { return defaultTheme; };
applyTheme.generateTheme = function(name) { generateTheme(THEMES[name], name, themeConfig.nonce); };
applyTheme.setBrowserColor = enableBrowserColor;

return applyTheme;

Expand Down Expand Up @@ -769,6 +853,7 @@ function generateAllThemes($injector, $mdTheming) {
}

palette[hueName] = {
hex: palette[hueName],
value: rgbValue,
contrast: getContrastColor()
};
Expand Down
Loading

0 comments on commit 57f2afd

Please sign in to comment.