117 lines
3.7 KiB
TypeScript
117 lines
3.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {Inject, Injectable} from '@angular/core';
|
|
|
|
import {DomAdapter, getDOM} from '../dom/dom_adapter';
|
|
import {DOCUMENT} from '../dom/dom_tokens';
|
|
|
|
|
|
/**
|
|
* Represents a meta element.
|
|
*
|
|
* @experimental
|
|
*/
|
|
export type MetaDefinition = {
|
|
charset?: string; content?: string; httpEquiv?: string; id?: string; itemprop?: string;
|
|
name?: string;
|
|
property?: string;
|
|
scheme?: string;
|
|
url?: string;
|
|
} &
|
|
{
|
|
// TODO(IgorMinar): this type looks wrong
|
|
[prop: string]: string;
|
|
};
|
|
|
|
/**
|
|
* A service that can be used to get and add meta tags.
|
|
*
|
|
* @experimental
|
|
*/
|
|
@Injectable()
|
|
export class Meta {
|
|
private _dom: DomAdapter;
|
|
constructor(@Inject(DOCUMENT) private _doc: any) { this._dom = getDOM(); }
|
|
|
|
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement|null {
|
|
if (!tag) return null;
|
|
return this._getOrCreateElement(tag, forceCreation);
|
|
}
|
|
|
|
addTags(tags: MetaDefinition[], forceCreation: boolean = false): HTMLMetaElement[] {
|
|
if (!tags) return [];
|
|
return tags.reduce((result: HTMLMetaElement[], tag: MetaDefinition) => {
|
|
if (tag) {
|
|
result.push(this._getOrCreateElement(tag, forceCreation));
|
|
}
|
|
return result;
|
|
}, []);
|
|
}
|
|
|
|
getTag(attrSelector: string): HTMLMetaElement|null {
|
|
if (!attrSelector) return null;
|
|
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`) || null;
|
|
}
|
|
|
|
getTags(attrSelector: string): HTMLMetaElement[] {
|
|
if (!attrSelector) return [];
|
|
const list /*NodeList*/ = this._dom.querySelectorAll(this._doc, `meta[${attrSelector}]`);
|
|
return list ? [].slice.call(list) : [];
|
|
}
|
|
|
|
updateTag(tag: MetaDefinition, selector?: string): HTMLMetaElement|null {
|
|
if (!tag) return null;
|
|
selector = selector || this._parseSelector(tag);
|
|
const meta: HTMLMetaElement = this.getTag(selector) !;
|
|
if (meta) {
|
|
return this._setMetaElementAttributes(tag, meta);
|
|
}
|
|
return this._getOrCreateElement(tag, true);
|
|
}
|
|
|
|
removeTag(attrSelector: string): void { this.removeTagElement(this.getTag(attrSelector) !); }
|
|
|
|
removeTagElement(meta: HTMLMetaElement): void {
|
|
if (meta) {
|
|
this._dom.remove(meta);
|
|
}
|
|
}
|
|
|
|
private _getOrCreateElement(meta: MetaDefinition, forceCreation: boolean = false):
|
|
HTMLMetaElement {
|
|
if (!forceCreation) {
|
|
const selector: string = this._parseSelector(meta);
|
|
const elem: HTMLMetaElement = this.getTag(selector) !;
|
|
// It's allowed to have multiple elements with the same name so it's not enough to
|
|
// just check that element with the same name already present on the page. We also need to
|
|
// check if element has tag attributes
|
|
if (elem && this._containsAttributes(meta, elem)) return elem;
|
|
}
|
|
const element: HTMLMetaElement = this._dom.createElement('meta') as HTMLMetaElement;
|
|
this._setMetaElementAttributes(meta, element);
|
|
const head = this._dom.getElementsByTagName(this._doc, 'head')[0];
|
|
this._dom.appendChild(head, element);
|
|
return element;
|
|
}
|
|
|
|
private _setMetaElementAttributes(tag: MetaDefinition, el: HTMLMetaElement): HTMLMetaElement {
|
|
Object.keys(tag).forEach((prop: string) => this._dom.setAttribute(el, prop, tag[prop]));
|
|
return el;
|
|
}
|
|
|
|
private _parseSelector(tag: MetaDefinition): string {
|
|
const attr: string = tag.name ? 'name' : 'property';
|
|
return `${attr}="${tag[attr]}"`;
|
|
}
|
|
|
|
private _containsAttributes(tag: MetaDefinition, elem: HTMLMetaElement): boolean {
|
|
return Object.keys(tag).every((key: string) => this._dom.getAttribute(elem, key) === tag[key]);
|
|
}
|
|
}
|