3

The styles are preserved through style inlining if I select and copy a nicely styled HTML table (or any other element) in a page and then paste it inside a new email message in Gmail or Outlook composing interface.

Example: enter image description here

I would like to trigger via Javascript this style-inlined HTML that Chrome generates for pasting, to generate an HTML to be used for generating a formatted XLS or an email message.

brauliobo
  • 5,843
  • 4
  • 29
  • 34

1 Answers1

5
Notes:
  • Version 3* due to misreading question
  • Copying to clipboard does not work in StackOverflow snippet, try offline

Original answer, using getComputedStyle()

We can use getComputedStyle() to get all the computed styling for an element.

We can map this to inline css and add it to the DOM using setAttribute

Since getComputedStyle() literally returns all the styling, most of them won't be needed. I'd recommend using a whitelist style. (See second snippet below)

Small demo, press the button to copy the text.
All the div style's will be inline, somewhere in that huge list will be border etc.

function getOuterHTMLWithInlineStyle(el) {
    let s = getComputedStyle(el);
    let i = []
    for (let key in s) {
        if (!Number.isInteger(+key)) {
          let prop = key.replace(/\-([a-z])/g, v => v[1].toUpperCase());
          i.push(`${key}: ${s[key]}`);
        }
    }
    
    el.setAttribute('style', i.join("; "));
    
    return el.outerHTML;
}

function copy() {
    const e = document.querySelector('div')
    const html = getOuterHTMLWithInlineStyle(e)
       
    navigator.clipboard.writeText(html).then(() => {
      console.log('Succesfully copied HTML to clipboard');
    }, function(err) {
      console.error('Could not copy text.\nTried to copy the following:\n', html);
    });
}
div {
    padding: 5px 25px;
    border: 1px solid red; 
}
<div>Hello World!</div>
<br>
<button onClick='copy()'>Click me to copy</button>

Second answer, using a whitelist

Same demo as above, but with the whitelist added: Will copy the following HTML:

<div style="border: 1px solid rgb(255, 0, 0); padding: 5px 25px">Hello World!</div>

function getOuterHTMLWithInlineStyle(el, whitelist = [ 'border', 'padding' ]) {
    let s = getComputedStyle(el);
    let i = []
    for (let key in s) {
        if (whitelist.includes(key)) {
          let prop = key.replace(/\-([a-z])/g, v => v[1].toUpperCase());
          i.push(`${key}: ${s[key]}`);
        }
    }
    
    el.setAttribute('style', i.join("; "));
    
    return el.outerHTML;
}

function copy() {
    const e = document.querySelector('div')
    const html = getOuterHTMLWithInlineStyle(e)
       
    navigator.clipboard.writeText(html).then(() => {
      console.log('Succesfully copied HTML to clipboard');
    }, function(err) {
      console.error('Could not copy text.\nTried to copy the following:\n', html);
    });
}
div {
    padding: 5px 25px;
    border: 1px solid red; 
}
<div>Hello World!</div>
<br>
<button onClick='copy()'>Click me to copy</button>

Third answer, using document.styleSheets

Oke, v3, based on this SO answer where the user uses document.styleSheets to get only the css from stylesheets and filter that on those who are non-default.

Using a map() and regex, we can get a string with just containing the CSS values you need.

Note this might not work 100%, the regex does not take into account that CSS values might contain {'s. You should improve the regex if you have very complex values.

The (collapsed) snippet below will output/copy the following HTML:

<div style="padding: 5px 25px; border: 1px solid red;">Hello World!</div>

var proto = Element.prototype;
var slice = Function.call.bind(Array.prototype.slice);
var matches = Function.call.bind(proto.matchesSelector || 
                proto.mozMatchesSelector || proto.webkitMatchesSelector ||
                proto.msMatchesSelector || proto.oMatchesSelector);

var elementMatchCSSRule = function(element, cssRule) {
  return matches(element, cssRule.selectorText);
};

var cssRules = slice(document.styleSheets).reduce(function(rules, styleSheet) {
  return rules.concat(slice(styleSheet.cssRules));
}, []);

var getAppliedCss = function(elm) {
    var elementRules = cssRules.filter(elementMatchCSSRule.bind(null, elm));
    var rules =[];
    if (elementRules.length) {
        for (i = 0; i < elementRules.length; i++) {
            rules.push(elementRules[i].cssText)
        }       
    }
  
    return rules.map(r => /\{(.+)\}/g.exec(r)[1].trim()).join("; ");
}

function getOuterHTMLWithInlineStyle(el) {
    el.setAttribute('style', getAppliedCss(el));    
    return el.outerHTML;
}

function copy() {
    const e = document.querySelector('div')
    const html = getOuterHTMLWithInlineStyle(e)
       
    navigator.clipboard.writeText(html).then(() => {
      console.log('Succesfully copied HTML to clipboard');
    }, function(err) {
      console.error('Could not copy text.\nTried to copy the following:\n', html);
    });
}
div {
    padding: 5px 25px;
    border: 1px solid red; 
}
<div>Hello World!</div>
<br>
<button onClick='copy()'>Click me to copy</button>
0stone0
  • 34,288
  • 4
  • 39
  • 64
  • 1
    They are after the built in behavior that when you copy styled text the styles are extracted, even from external stylesheets, and then appended to the copied markup as inline styles. https://jsfiddle.net/hkrz21g4/ The current solution would only work for the styles that were already in the markup. – Kaiido Nov 07 '22 at 12:51
  • @Kaiido this is what I want, but directly via javascript and without user copy/paste action – brauliobo Nov 07 '22 at 13:00
  • What do you mean by 'without user copy/paste action'? Without the button? – 0stone0 Nov 07 '22 at 13:01
  • @0stone0 your solution would work if the css is already inlined. My HTML CSS isn't inline, so the script needs to do the inlining – brauliobo Nov 07 '22 at 13:10
  • 1
    I've changed my answer. Hope this helps. – 0stone0 Nov 07 '22 at 13:17
  • 1
    Works nicely but includes all the default style values that any browser would add itself, whereas the inspector listing shown in the question only includes styles that change default values and are needed to achieve the desired page. This results in the style attribute string for that single div element, containing over 27,000 characters. credit where due, it's a good answer and maybe perfection should not be the enemy of success. – Dave Pritlove Nov 07 '22 at 13:56
  • Yea as stated in my answer, thats a downside of getComputedStyle, it will show all! The second snipet uses a witelist, I think that might be the way to go. YOu could also filter on the value, so ignore all those keys where the value is eeather 0, null, unset etc. – 0stone0 Nov 07 '22 at 14:04
  • I've added s third snippet to my answer @brauliobo. Hope that helps. – 0stone0 Nov 07 '22 at 14:39