Reputation: 1512
We have a corporate content management system that allows for rich text editing/html markup, but does not allow for head elements or style sheets to be uploaded, attached, or used in any way. It provides some rich text editing controls and also access to the source html, but just for the html fragment -- there is no head, no body. We also have no access the whole system that presents these bits of markup on the page. The only way to style the content is through inline style attributes on the elements. It is best, it isn't pretty, but that is what we have and I'm trying to make the best of a bad situation.
We also have high standards for visual presentation and would like to be able to quickly produce and modify/update content and keep it looking nice. It is difficult to correctly apply formatting using the system. For anybody who has tried to markup anything more than a paragraph or two with an RTE, you probably know what I mean. It seems like we should have a different system, but has anybody worked for a large company before? Just sayin.
We do have access to another location where we could "author" and "store" actual styled content and then "compile it" for copypasta into the other system. In other words, we could author/design using css and best practices and then we could run some code that could convert those element, class, and id formatting into inline styles.
I did my research and found this thread which also lead me to this code.
These both are very helpful in exploring solutions, but I've run into an issue. These solutions use the javascript getComputedStyle() method. There are some other options for properties to only look at other properties or to be recursive on the children of the element provide, but basically it boils down to this. (Since getComputeStyle returns an object and not an array, there is also a prototype/polyfill to allow iterating over an object with forEach, but none of that is part of the issue I'm facing.)
const computedStyle = getComputedStyle(element); computedStyle.forEach(property => { element.style[property] = computedStyle.getPropertyValue(property); });
This works well for css attributes like font-size:24px or margin:0 15px. The issue I'm running into are when I'm using units other than px. For example, if I'm trying to make something that has width:50%. getComputedStyle() converts the 50% to the actual number of pixels that 50% is currently using.
In the notes section of the MDN web docs I see that this is expected behavior. Although I'm not quite clear on what that last line means.
...An example difference between pre- and post-layout values includes the resolution of percentages for width or height, as those will be replaced by their pixel equivalent only for used values.
So what I'm trying to do is convert something like this
.container{width:50%;}
<div class="container">
into something like this
<div class="container" style="width:50%">
Does anyone know of a way to complete this type of transformation?
PS: If it matters we'll be using the more basic attributes in our css -- no transitions, grid, prefixing, etc. We still need to support IE 11 -- if that tells you anything. We won't need to account for every edge case or browser. Just some basic stuff so that all our H1 look the same.
Upvotes: 3
Views: 3218
Reputation: 945
Convert style classes into inline styles for each element in the document. This method is more efficient than the previous ones.
function applyInlineStyleForEntireDocument() {
for (const sheet of document.styleSheets) {
for (const rule of sheet.cssRules) {
const styleContent = rule
.cssText
.split('{')
.pop()
.split('}')
.shift();
const className = rule
.cssText
.split('{')
.shift()
.trim();
document.querySelectorAll(className).forEach(e => {
e.classList.remove(className.replace('.', '').trim());
const existingStyleContent = e.getAttribute('style') ?? ''
const newStyleContent = existingStyleContent + styleContent;
e.setAttribute('style', newStyleContent);
})
}
}
}
Upvotes: 0
Reputation: 1696
Here is a cleaned up and modernized version of the accepted answer.
function applyInline(element) {
const elements = [element, ...element.querySelectorAll("*")];
const elementRules = document.createElement(element.tagName).style;
elementRules.cssText = element.style.cssText;
for (const sheet of Object.values(document.styleSheets)) {
let cssRules = {};
try {
cssRules = sheet.cssRules;
} catch (error) {
//
}
for (const rule of Object.values(cssRules))
for (const element of elements)
if (element.matches(rule.selectorText))
for (const prop of rule.style)
element.style.setProperty(
prop,
elementRules.getPropertyValue(prop) ||
rule.style.getPropertyValue(prop),
rule.style.getPropertyPriority(prop)
);
}
}
I've thrown in error handling for when you try to access a style sheet that doesn't have the appropriate permissions.
VM49845:1 Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
For example, in my case I have Google Fonts stylesheets that were causing this error. Apparently you can also do crossorigin="anonymous"
on the <link>
to avoid that error.
I think the use of querySelectorAll
is also a simpler and more flexible way to get all the children recusively.
Upvotes: 1
Reputation: 1512
Couldn't find any way to do this using the built in getComputedStyle(). It also returned too many properties that I wasn't interested in. So I came up with a different approach. Basically to use the same function to loop through an element (and maybe all its children elements) and the use Element.matches() to get all the css rules that apply to the element and apply the properties as they were specified in the stylesheet.
I modified this answer a bit to get the rules from the stylesheet.
Has the added benefit that we can pull either from all the document stylesheets or just from a specific one that is needed for preparing the code to go into our content management systems's rich text editor.
function applyInline(element, recursive = true) {
if (!element) {
throw new Error("No element specified.");
}
const matches = matchRules(element);
// we need to preserve any pre-existing inline styles.
var srcRules = document.createElement(element.tagName).style;
srcRules.cssText = element.style.cssText;
matches.forEach(rule => {
for (var prop of rule.style) {
let val = srcRules.getPropertyValue(prop) || rule.style.getPropertyValue(prop);
let priority = rule.style.getPropertyPriority(prop);
element.style.setProperty(prop,val,priority);
}
});
if (recursive) {
element.children.forEach(child => {
applyInline(child, recursive);
});
}
}
function matchRules(el, sheets) {
sheets = sheets || document.styleSheets;
var ret = [];
for (var i in sheets) {
if (sheets.hasOwnProperty(i)) {
var rules = sheets[i].rules || sheets[i].cssRules;
for (var r in rules) {
if (el.matches(rules[r].selectorText)) {
ret.push(rules[r]);
}
}
}
}
return ret;
}
Upvotes: 3