How do you avoid repeating styles in shadow DOM? - web-component

I'm new to web components, so not sure if there's an easy answer or best practice on this one.
If I want to create a series of repeating elements with encapsulated style how do I avoid repeating the style block with each element.
A reduced version of the code I'm using is:
<ul id="wrapper"></ul>
<template id="template">
<style>
li { color: red }
</style>
<li></li>
</template>
<script>
var wrapper = document.getElementById('wrapper');
var tpl = document.getElementById('template');
var shadow = wrapper.createShadowRoot();
var arr = ['a', 'b', 'c'];
for(var i = 0, ii = arr.length; i < ii; i++) {
tpl.content.querySelector('li').textContent = arr[i];
var clone = document.importNode(tpl.content, true);
shadow.appendChild(clone);
}
</script>
The problem I have with that is that the shadow DOM has a duplicated style block for each <li>, and am not sure the best way round it.
I have tried nesting a template and that is either never going to work or I'm not doing it right.

Fast-forward to 2022 and we now have "constructable stylesheets". Reference here. Basically the browser is smart enough to use only a single constructed stylesheet for all those copies of your webcomponent on the page! :)
Quoting from the article:
Constructable Stylesheets make it possible to define and prepare shared CSS styles, and then apply those styles to multiple Shadow Roots or the Document easily and without duplication. Updates to a shared CSSStyleSheet are applied to all roots into which it has been adopted

How about checking for a style element and importing it separately?
<ul id="wrapper"></ul>
<template id="template">
<style>
li { color: red }
</style>
<li></li>
</template>
<script>
var wrapper = document.getElementById('wrapper');
var tpl = document.getElementById('template').cloneNode(true);
var shadow = wrapper.createShadowRoot();
var style = tpl.content.querySelector('style');
if (style) {
tpl.content.removeChild(style);
shadow.appendChild(document.importNode(style, true));
}
var arr = ['a', 'b', 'c'];
for(var i = 0, ii = arr.length; i < ii; i++) {
tpl.content.querySelector('li').textContent = arr[i];
shadow.appendChild(document.importNode(tpl.content, true));
}
</script>

Related

Hide custom cursor on hover over selected div

I have a custom cursor with some hover effects on a page (wordpress). This custom cursor shall hide, when hovering a certain div, due to some text. Please take a look here: https://florianwmueller.com/work-test/
I tried a lot, including some javascript, but nothing works. Any idea?
I gave the pictures a special class: .no-cursor
Thankful for any help...
Add this script on your website.
<script>
var elements = document.getElementsByClassName("no-cursor");
var style;
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener("mouseover", function() {
style = document.createElement("style");
style.innerHTML = "body.cursor-element-shape a { cursor: default !important; } .wpcc-active > .wpcc-cursor { display: none !important; }";
document.head.appendChild(style);
});
elements[i].addEventListener("mouseleave", function() {
document.head.removeChild(style);
});
}
</script>

target and rel attributes not working on style.css

I want to have these 2 attributes in a class on my stylesheet: target="_blank" and rel="noopener noreferrer"
The attributes work perfectly fine when I include them directly in a tag (example).
But when I try to put the 2 into a class (below), I get an error where target and rel are not recognized as attributes.
.link {
target: _blank
rel: noopener noreferrer
}
You need to do this using JavaScript, not CSS
Add an event listener to the document that waits for the document to be loaded, then get all of the elements with the .link class and set the relevant attributes.
document.addEventListener("DOMContentLoaded", function() {
var links = document.querySelectorAll(".link");
var numLinks = links.length;
for (var i = 0; i < numLinks; i++) {
var link = links[i];
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener noreferrer");
}
});

Hide a whole div with CSS with part of it is empty

Is there a way to hide a whole div if part of it is empty? For example if "dd" is empty as shown below can I hide the whole class "test" so the keyword Restrictions does not show either. I tried .test dd:empty { display: none; } but this does not work. thanks!
<div class="test"><dt>Restrictions:</dt>
<dd></dd></div>
I don't think there's any easy way to do what you're talking about with just CSS. Better to test it server-side if you can. But if you can't here's some JS that will do the job.
<script type="text/javascript">
// handles multiple dt/dd pairs per div and hides them each conditionally
function hideIfEmpty() {
// get all the elements with class test
var els = document.getElementsByTagName('dl');
// for every 'test' div we find, go through and hide the appropriate elements
Array.prototype.map.call(els, function(el) {
var children = el.childNodes;
var ddEmpty = false;
for(var i = children.length - 1; i >= 0; i--) {
if(children[i].tagName === 'DD' && !children[i].innerHTML.trim()) {
ddEmpty = true;
} else if(children[i].tagName === 'DT') {
if(ddEmpty) {
children[i].style.display = 'none';
}
// reset the flag
ddEmpty = false;
}
}
});
}
window.addEventListener('load', hideIfEmpty);
</script>
<div class="test">
<div style="clear: both;"></div>
<dl>
<dt>Restrictions:</dt>
<dd></dd>
<dt>Other Restrictions:</dt>
<dd>Since I have content, I won't be hidden.</dd>
</dl>
</div>
Just a fair warning: the code uses some functions that may not exist in older IE, such as Array.prototype.map, String.prototype.trim, and addEventListener. There are polyfills available for these and you could also write your own pretty easily (or just do it with a for loop instead).
CSS alone can't do that. Either, you need a javascript to retrieve empty elements and hide their parents, or your CMS applies special CSS classes if there's no content.
Put as an answer as requested by #Barett.
You could update your CSS to be
.test{
display: none;
color: transparent;
}
This would make the text transparent too, but display:none should hide it anyway.
To make the div with the id test ONLY show when the dd tag is EMPTY, and you can use jQuery, try the following JavaScript along with the CSS:
if($("dd").html().length ==0)
{show();
}
Note: this solution requires jQuery, which is a JavaScript library.

Javascript add style tag to head

I'm trying to add some css to an internal stylesheet with javascript. This is the code I've used:
function googleJS(){
var iframe = document.getElementsByTagName('iframe')[0];
var doc = iframe.contentWindow.document;
var newScript = doc.createElement('style');
newScript.setAttribute('.skiptranslate { display: none; }');
var bodyClass = doc.getElementsByTagName('head')[0];
bodyClass.insertBefore(newScript, bodyClass.childNodes[2]);
}
However this results in:
<style .skiptranslate {display: none};></style>
What is the proper method to use javascript to insert a style tag with the necessary CSS inside the DOM?
Thanks
As the name implies (and documentation says) Element.setAttribute:
Adds a new attribute or changes the value of an existing attribute on the specified element.
which is exactly what's happening on the <style> element. To fix this, use .textContent/.innerText or a text element instead of .setAttribute().
function googleJS(){
var iframe = document.getElementsByTagName('iframe')[0];
var doc = iframe.contentWindow.document;
var newScript = doc.createElement('style');
var content = doc.createTextNode('.skiptranslate { display: none; }');
newScript.appendChild(content);
var bodyClass = doc.getElementsByTagName('head')[0];
bodyClass.insertBefore(newScript, bodyClass.childNodes[2]);
}
If you can use JQuery:
$().css('Property-Name', 'Value');

Export CSS of DOM elements

I often find nice stylings on the web. To copy the CSS of a DOM element, I inspect that element with Google Chrome Developer Tools, look at the various CSS properties, and copy those manually to my own stylesheets.
Is it possible to easily export all CSS properties of a given DOM element?
Here is the code for an exportStyles() method that should return a CSS string including all inline and external styles for a given element, except default values (which was the main difficulty).
For example: console.log(someElement.exportStyles());
Since you are using Chrome, I did not bother making it compatible with IE.
Actually it just needs that the browsers supports the getComputedStyle(element) method.
Element.prototype.exportStyles = (function () {
// Mapping between tag names and css default values lookup tables. This allows to exclude default values in the result.
var defaultStylesByTagName = {};
// Styles inherited from style sheets will not be rendered for elements with these tag names
var noStyleTags = {"BASE":true,"HEAD":true,"HTML":true,"META":true,"NOFRAME":true,"NOSCRIPT":true,"PARAM":true,"SCRIPT":true,"STYLE":true,"TITLE":true};
// This list determines which css default values lookup tables are precomputed at load time
// Lookup tables for other tag names will be automatically built at runtime if needed
var tagNames = ["A","ABBR","ADDRESS","AREA","ARTICLE","ASIDE","AUDIO","B","BASE","BDI","BDO","BLOCKQUOTE","BODY","BR","BUTTON","CANVAS","CAPTION","CENTER","CITE","CODE","COL","COLGROUP","COMMAND","DATALIST","DD","DEL","DETAILS","DFN","DIV","DL","DT","EM","EMBED","FIELDSET","FIGCAPTION","FIGURE","FONT","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEAD","HEADER","HGROUP","HR","HTML","I","IFRAME","IMG","INPUT","INS","KBD","KEYGEN","LABEL","LEGEND","LI","LINK","MAP","MARK","MATH","MENU","META","METER","NAV","NOBR","NOSCRIPT","OBJECT","OL","OPTION","OPTGROUP","OUTPUT","P","PARAM","PRE","PROGRESS","Q","RP","RT","RUBY","S","SAMP","SCRIPT","SECTION","SELECT","SMALL","SOURCE","SPAN","STRONG","STYLE","SUB","SUMMARY","SUP","SVG","TABLE","TBODY","TD","TEXTAREA","TFOOT","TH","THEAD","TIME","TITLE","TR","TRACK","U","UL","VAR","VIDEO","WBR"];
// Precompute the lookup tables.
for (var i = 0; i < tagNames.length; i++) {
if(!noStyleTags[tagNames[i]]) {
defaultStylesByTagName[tagNames[i]] = computeDefaultStyleByTagName(tagNames[i]);
}
}
function computeDefaultStyleByTagName(tagName) {
var defaultStyle = {};
var element = document.body.appendChild(document.createElement(tagName));
var computedStyle = getComputedStyle(element);
for (var i = 0; i < computedStyle.length; i++) {
defaultStyle[computedStyle[i]] = computedStyle[computedStyle[i]];
}
document.body.removeChild(element);
return defaultStyle;
}
function getDefaultStyleByTagName(tagName) {
tagName = tagName.toUpperCase();
if (!defaultStylesByTagName[tagName]) {
defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
}
return defaultStylesByTagName[tagName];
}
return function exportStyles() {
if (this.nodeType !== Node.ELEMENT_NODE) {
throw new TypeError("The exportStyles method only works on elements, not on " + this.nodeType + " nodes.");
}
if (noStyleTags[this.tagName]) {
throw new TypeError("The exportStyles method does not work on " + this.tagName + " elements.");
}
var styles = {};
var computedStyle = getComputedStyle(this);
var defaultStyle = getDefaultStyleByTagName(this.tagName);
for (var i = 0; i < computedStyle.length; i++) {
var cssPropName = computedStyle[i];
if (computedStyle[cssPropName] !== defaultStyle[cssPropName]) {
styles[cssPropName] = computedStyle[cssPropName];
}
}
var a = ["{"];
for(var i in styles) {
a[a.length] = i + ": " + styles[i] + ";";
}
a[a.length] = "}"
return a.join("\r\n");
}
})();
This code is base on my answer for a slightly related question: Extract the current DOM and print it as a string, with styles intact
I'm quoting Doozer Blake's excellent answer, provided above as a comment. If you like this answer, please upvote his original comment above:
Not a direct answer, but with Chrome Developer Tools, you can click inside Styles or Computed Styles, hit Ctrl+A and then Ctrl+C to copy all the styles in those given areas. It's not perfect in the Style tab because it picks up some extra stuff. Better than selecting them one by one I guess. – Doozer Blake 3 hours ago
You can do the same using Firebug for Firefox, by using Firebug's "Computed" side panel.
There are a few ways to almost do this.
Have a look at FireDiff
Also have a look at cssUpdater This is for local CSS only]
And see this Q for more similar tools: Why can't I save CSS changes in Firebug?
Also this paid product claims to be able to do this: http://www.skybound.ca/

Resources