From a5ebd5b7b13dd7fbbc5cc68c362fe3782439c053 Mon Sep 17 00:00:00 2001 From: Michael Mrowetz Date: Tue, 6 Jun 2017 12:26:23 +0900 Subject: [PATCH] safeSetStyle / safeSetAttribute helpers globalised --- src/ts/helpers/dom.ts | 51 ++++++++++++++++++- src/ts/helpers/svg.ts | 38 +++----------- .../details-overlay/html-details-body.ts | 7 ++- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/ts/helpers/dom.ts b/src/ts/helpers/dom.ts index 21fa495d..f5ffae42 100644 --- a/src/ts/helpers/dom.ts +++ b/src/ts/helpers/dom.ts @@ -71,7 +71,56 @@ export function getLastItemOfNodeList(list: NodeListOf) { return list.item(list.length - 1); } -// /** Calls `fn` with each element of `els` */ +/** Calls `fn` with each element of `els` */ export function forEachNodeList(els: NodeListOf, fn: (el: T, index: number) => any) { Array.prototype.forEach.call(els, fn); } + +interface StringOrNumberMap { [key: string]: string | number; } + +/** Sets a CSS style property, but only if property exists on `el` */ +export function safeSetStyle(el: HTMLElement | SVGElement, property: string, value: string) { + if (property in el.style) { + el.style[property] = value; + } else { + console.warn(new Error(`Trying to set non-existing style ` + + `${property} = ${value} on a <${el.tagName.toLowerCase()}>.`)); + } +} + +/** Sets an attribute, but only if `name` exists on `el` */ +export function safeSetAttribute(el: HTMLElement | SVGElement, name: string, value: string) { + if (!(name in el)) { + console.warn(new Error(`Trying to set non-existing attribute ` + + `${name} = ${value} on a <${el.tagName.toLowerCase()}>.`)); + } + el.setAttributeNS(null, name, value); +} + +/** Sets multiple CSS style properties, but only if property exists on `el` */ +export function safeSetStyles(el: HTMLElement | SVGElement, css: StringOrNumberMap) { + Object.keys(css).forEach((property) => { + safeSetStyle(el, property, css[property].toString()); + }); +} + +/** Sets attributes, but only if they exist on `el` */ +export function safeSetAttributes(el: HTMLElement | SVGElement, attributes: StringOrNumberMap) { + Object.keys(attributes).forEach((name) => { + safeSetAttribute(el, name, attributes[name].toString()); + }); +} + +export function makeHtmlEl() { + const html = document.createElement("html"); + html.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://www.w3.org/2000/xmlns/"); + return html; +} + +export function makeBodyEl(css: StringOrNumberMap = {}, innerHTML = "") { + const body = document.createElement("body"); + body.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); + safeSetStyles(body, css); + body.innerHTML = innerHTML; + return body; +} diff --git a/src/ts/helpers/svg.ts b/src/ts/helpers/svg.ts index 115282c5..96cec259 100644 --- a/src/ts/helpers/svg.ts +++ b/src/ts/helpers/svg.ts @@ -2,7 +2,7 @@ * SVG Helpers */ -import { addClass } from "./dom"; +import { addClass, safeSetAttributes, safeSetStyles } from "./dom"; export interface StringToStringOrNumberMap { [key: string]: string | number; } export type DomAttributeMap = StringToStringOrNumberMap; @@ -11,33 +11,10 @@ export type CssStyleMap = StringToStringOrNumberMap; /** Namespace for SVG Elements */ const svgNamespaceUri = "http://www.w3.org/2000/svg"; -function entries(obj: StringToStringOrNumberMap): Array<[string, string]> { - const entries: Array<[string, string]> = []; - for (const k of Object.keys(obj)) { - entries.push([k, String((obj[k]))]); - } - return entries; -} - -function safeSetAttribute(el: SVGElement, key: string, s: string) { - if (!(key in el)) { - console.warn(new Error(`Trying to set non-existing attribute ${key} = ${s} on a <${el.tagName.toLowerCase()}>.`)); - } - el.setAttributeNS(null, key, s); -} - interface StylableSVGElement extends SVGElement { readonly style: CSSStyleDeclaration; } -function safeSetStyle(el: StylableSVGElement, key: string, s: string) { - if (key in el.style) { - el.style[key] = s; - } else { - console.warn(new Error(`Trying to set non-existing style ${key} = ${s} on a <${el.tagName.toLowerCase()}>.`)); - } -} - interface SvgElementOptions { attributes?: DomAttributeMap; css?: CssStyleMap; @@ -58,9 +35,8 @@ function newElement(tagName: string, { if (text) { element.textContent = text; } - entries(css).forEach(([key, value]) => safeSetStyle(element, key, value)); - entries(attributes).forEach(([key, value]) => safeSetAttribute(element, key, value)); - + safeSetStyles(element, css); + safeSetAttributes(element, attributes); return element; } @@ -77,8 +53,8 @@ export function newClipPath(id: string): SVGClipPathElement { return newElement("clipPath", { attributes }); } -export function newForeignObject(attributes: DomAttributeMap) { - return newElement("foreignObject", { attributes }); +export function newForeignObject(attributes: DomAttributeMap, className = "", css: CssStyleMap = {}) { + return newElement("foreignObject", { attributes, className, css }); } export function newA(className: string): SVGAElement { @@ -86,13 +62,13 @@ export function newA(className: string): SVGAElement { } export function newRect(attributes: DomAttributeMap, - className: string = "", + className = "", css: CssStyleMap = {}) { return newElement("rect", { attributes, className, css }); } export function newLine(attributes: DomAttributeMap, - className: string = "") { + className = "") { return newElement("line", { className, attributes }); } diff --git a/src/ts/waterfall/details-overlay/html-details-body.ts b/src/ts/waterfall/details-overlay/html-details-body.ts index 15ee945c..3c130d1c 100644 --- a/src/ts/waterfall/details-overlay/html-details-body.ts +++ b/src/ts/waterfall/details-overlay/html-details-body.ts @@ -1,3 +1,4 @@ +import { makeBodyEl, makeHtmlEl } from "../../helpers/dom"; import { escapeHtml, sanitizeUrlForLink } from "../../helpers/parse"; import { WaterfallEntry } from "../../typing/waterfall"; @@ -11,10 +12,8 @@ import { WaterfallEntry } from "../../typing/waterfall"; */ export function createDetailsBody(requestID: number, detailsHeight: number, entry: WaterfallEntry) { - const html = document.createElement("html") as HTMLHtmlElement; - const body = document.createElement("body"); - body.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); - html.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://www.w3.org/2000/xmlns/"); + const html = makeHtmlEl(); + const body = makeBodyEl(); const tabMenu = entry.tabs.map((t) => { return `
  • `;