CSS selector for empty or whitespace - html

We have a selector :empty that can match an element when it is completely empty:
<p></p>
But I have a condition where it might be empty, or it might contain line breaks or blank spaces:
<p> </p>
I found a solution for Firefox, :-moz-only-whitespace:
:empty { width: 300px; height: 15px; background: blue; }
:-moz-only-whitespace { width: 300px; height: 15px; background: orange; }
<p></p>
<p> </p>
<p>This is paragraph three.</p>
Is there a similar solution for other browsers?
PS: In JSFiddle

Lots of people missing the point of this question, which I've addressed in the following exposition, but for those just looking for the answer, I'm mirroring the last paragraph here:
Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
While the question depicts a <p> element containing a handful of regular space characters, which seems like an oversight, it is far more common to see markup where elements contain only whitespace in the form of indentation and blank lines, such as:
<ul class="items">
<li class="item">
<div>
<!-- Some complex structure of elements -->
</div>
</li>
<li class="item">
</li> <!-- Empty, except for a single line break and
indentation preceding the end tag -->
</ul>
Some elements, like <li> in the above example as well as <p>, have optional end tags, which can cause unintended side effects in DOM processing as well in the presence of inter-element whitespace. For example, the following two <ul> elements don't produce equivalent node trees, in particular the first one does not result in a li:empty in Selectors level 3:
li:empty::before { content: '(empty)'; font-style: italic; color: #999; }
<ul>
<li>
</ul>
<ul>
<li></li>
</ul>
Given that HTML considers inter-element whitespace to be transparent by design, it's not unreasonable to want to target such elements with CSS without having to resort to modifying the HTML or the application generating it (especially if you end up having to implement and test a special case just to do so). To that end, Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.

#BoltClock provided a fantastic answer to this question, showing that this (currently, that is, working with CSS Specification 3) cannot be achieved by CSS alone.
#BoltClock mentioned that elements that are truly empty (which is a weird definition as explained) can be targeted by using the pseudo selector :empty. This pseudo selector is only available in CSS 3 and WILL NOT select elements that have only whitespace as content.
#BoltClock stated that the only way to clean up elements that have only whitespace as content is to fix the HTML, but that is not entirely correct. This can also be achieved through the implementation of Javascript.
KEEP IN MIND! The Javascript that I am offering to solve this issue may take a very long time to execute, so the best method is to clean up the raw HTML instead if possible. If that is not possible, then this may work as a solution, as long as you do not have too extensive of a DOM tree.
I'll walk through the steps of how to write the script yourself...
First of all, launch everything after page load.
This should be pretty obvious. You need to make sure that the DOM has fully loaded before running your script. Add an event listener for page load:
window.addEventListener("load", cleanUpMyDOM);
...and, of course, before that, create a function called cleanUpMyDOM. We will write the rest of our logic within this function.
Second, gather the elements that we are checking.
In our example we are going to check the entire DOM, but this is where our script can get VERY extensive and may make your page unresponsive. You may want to limit the amount of nodes you are iterating over.
We can grab the nodes in question by using the document.querySelectorAll. What's nice about this function is that it will level out the DOM tree and we won't have to recurse the children of each node.
var nodes = document.querySelectorAll("*");
As I said earlier, this code will grab EVERY DOM node, and that is probably NOT a good idea.
For example, I am working with WordPress, and some of the internal pages have some junk in them. Luckily, they are all p elements that are children of a div.body element, so I can change my selector to document.querySelectorAll("div.body p"), which will select only p elements that are children of my div.body element recursively. This will greatly optimize my script.
Third, iterate the nodes and find the empty ones.
We'll create a loop for the nodes array and check each node in it. We will then have to check to see if the node is empty. If it is empty, we'll apply a class to it called blank.
I'm just shooting from the hip here, so if you notice a mistake in this code, please let me know.
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
I am sure that there is a cleaner way to write the loop above, but this should get the job done.
Lastly, all you need to do is target the blank elements with your CSS.
Add this rule to your stylesheet:
.blank {
display:none;
}
And there you have it! All of your "blank" nodes have been hidden.
For those who just want to jump ahead, here is the finished script:
function cleanUpMyDOM(){
var nodes = document.querySelectorAll("*");
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
}
window.addEventListener("load", cleanUpMyDOM);
Once again, if you notice any issues with my code, please let me know in the comments below.
Hope this helps!
P.S. Many people may be wondering why you would want to do this, as it does feel like bad practice. I would avoid doing this, but I am in a situation where I am starting to consider it. The content of the pages on my site are created through a WYSIWYG editor. This content is created and modified constantly by the marketing team and I get pretty overwhelmed handling the support for their slip-ups. Its not my job to fix WordPress's WYSIWYG editor (nor would I ever want to), but I could write a very simple script that can handle some of the work for me. That definitely seems like the better answer to me, besides training the support team on managing their whitespace when making edits.

For anyone looking at the exact link: https://drafts.csswg.org/selectors-4/Overview.bs#the-empty-pseudo
And TL;DR:
Note: In Level 2 and Level 3 of Selectors, '':empty'' did not match
elements that contained only white space. This was changed so that
that-- given white space is largely collapsible in HTML and is
therefore used for source code formatting, and especially because
elements with omitted end tags are likely to absorb such white space
into their DOM text contents-- elements which authors perceive of as
empty can be selected by this selector, as they expect.
And :empty will consider spaces as empty from v4 onwards:
content nodes (such as [[DOM]] text nodes, and entity references) whose data has a non-zero length must be considered as affecting emptiness; comments, processing instructions, and other nodes must not affect whether an element is considered empty or not.

Related

Single global CSS for Web App, no extra import, no exportparts

After carefully reading all related articles and posts on many sites, there is still one remaining question: Can i have a single, exchangeable CSS for a web app built with web components widhtout having to deal with all the weird stuff suggested by W3C?
I know about ::part( something ) and exportparts="something" to access nested components, but that does not go down the tree, so I have to add a part attribute to almost every element, which totally bloats my HTML.
Having an #import rule in each component is also not a great option, because it would be one more HTTP request per stylesheet. Also, once loaded in a template, the importet css can not be exchanged easily.
W3C really makes our lives harder by removing /deep/ and ::shadow. I know, performance concerns, blah, blah, but at least that worked like a charm.
Possible solutions I find impractical:
How to style slotted parts from the parent downwards
::slotted CSS selector for nested children in shadowDOM slot
How to access elements inner two shadow dom
Example HTML where all nested elements would be styleable with global CSS:
<body>
<o-app>
#shadowDOM
<o-header exportparts="username:o-textinput__username,action-ok:o-action__action-ok,o-action__label" part="o-header">
#shadowDOM
<o-texinput part="username">
<o-action exportparts="label:o-action__label" part="action-ok">
#shadowDOM
<div part="label">
Then I can finally style the label div by selecting it with:
::part( o-action__label ) {}
Now tell me that having to specify every single part of all descendant elements in the parent elements is not a total mess!
Playaround on Codepen: https://codepen.io/5180/pen/jOyQNYq?editors=1111
Now in 2021 I would rather use light DOM only instead of forcing the shadow DOM to behave like its counterpart, because there is currently no easy method of piercing through the artificial boundary. It was in the spec – ::shadow and /deep/ – but got removed, so deal with it. ::theme() is not ready yet, The ::part() selector is useless for deep styling as I pointed out in my example.
Just use the light DOM (innerHTML) of your custom element to avoid deep styling/theming issues.

Add html element that is "invisible" or skipped by CSS selector rules

I want to build an external GUI that operates on a generic HTML piece that comes with associated CSS. In order to enable some functionalities of the GUI, I would need to create some "meta" HTML elements to contain parts of content and associate them with data.
Example:
<div id="root">
<foo:meta data-source="document:1111" data-xref="...">
sometext
<p class="quote">...</p>
</foo:meta>
<p class="other">...</p>
</div>
This HTML is auto-generated starting from already existing HTML that has associated CSS:
<div id="root">
sometext
<p class="quote">...</p>
<p class="other">...</p>
</div>
#root>p {
color:green;
}
#root>p+p {
color:red;
}
The problem is, when adding the <foo:meta> element, this breaks CSS child and sibling selectors. I am looking for a way for the CSS selectors to keep working when encapsulating content in this way. We have tried foo\:meta{display:contents} style, but, although it works in terms of hiding the meta element from the box renderer, it doesn't hide it from the selector matcher. We do not produce the HTML/CSS to be processed, so writing them in a certain way before processing is not an option. They come as they are, generic HTML documents with associated CSS.
Is there a way to achieve what we are looking for using HTML/CSS?
To restate, we are looking for a way to dynamically encapsulate parts of content in non-visual elements without breaking child and sibling CSS selectors. The elements should only be available to DOM traversal such as document.getElementsByTagName('foo:meta')
If I understood your problem correctly.I would suggest using the space between the grandparent and the child instead of a '>'. Also your selector is an id and not a class.
The selector you have put in selects the next level child that is the children. But adding the space in between enables you to select grandchildren too!
so you have do is this
#root .quote {
color:green;
}
Let me know if this helped.
A working css is here
So, after much fiddling and research, we came to the conclusion that this can't be done, even with ShadowDom, as even that would require massive CSS rewrites that might not preserve semantics.
However, for anyone stumbling upon this question, we came to the same end by employing the following (I'll be short, pointers only):
using two comments to mark where the tag would start/end, instead of an XML tag (eg. <!--<foo:bar data-source="1111">-->...content...<!--</foo:bar>-->)
these pointers work more or less like the markup equivalent of a DOM Range and they can work together with it.
this approach has the interesting advantage (as opposed to a single node) that it can start and end in different nodes, so it can span subtrees.
But this also breaks the XML structure when you try to recompose it. Also it's quite easy by manipulation to end up with the range end moving before the range start, multiple ranges overlapping etc.
In order to recompose it (to send to a next XML processor or noSQL XML database for cross-referencing), we need to make sure we avoid the XML-breaking manipulations described above; then, one only needs to convert encapsulated tags to regular tags by using string manipulation on the document (X)HTML (innerHtml, outerHtml, XMLSerializer) to get a clean XML which can be mined and cross-referenced for content.
We used the TreeWalker API for document scanning of comments, you might need it, although scanning the document for comments this way can be slow (works for us though). If you are bolder you can try using xPath, ie. document.evaluate('//comment()',document), seems to work but we don't trust all browsers comply.

Apply CSS for empty element including space and comments [duplicate]

We have a selector :empty that can match an element when it is completely empty:
<p></p>
But I have a condition where it might be empty, or it might contain line breaks or blank spaces:
<p> </p>
I found a solution for Firefox, :-moz-only-whitespace:
:empty { width: 300px; height: 15px; background: blue; }
:-moz-only-whitespace { width: 300px; height: 15px; background: orange; }
<p></p>
<p> </p>
<p>This is paragraph three.</p>
Is there a similar solution for other browsers?
PS: In JSFiddle
Lots of people missing the point of this question, which I've addressed in the following exposition, but for those just looking for the answer, I'm mirroring the last paragraph here:
Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
While the question depicts a <p> element containing a handful of regular space characters, which seems like an oversight, it is far more common to see markup where elements contain only whitespace in the form of indentation and blank lines, such as:
<ul class="items">
<li class="item">
<div>
<!-- Some complex structure of elements -->
</div>
</li>
<li class="item">
</li> <!-- Empty, except for a single line break and
indentation preceding the end tag -->
</ul>
Some elements, like <li> in the above example as well as <p>, have optional end tags, which can cause unintended side effects in DOM processing as well in the presence of inter-element whitespace. For example, the following two <ul> elements don't produce equivalent node trees, in particular the first one does not result in a li:empty in Selectors level 3:
li:empty::before { content: '(empty)'; font-style: italic; color: #999; }
<ul>
<li>
</ul>
<ul>
<li></li>
</ul>
Given that HTML considers inter-element whitespace to be transparent by design, it's not unreasonable to want to target such elements with CSS without having to resort to modifying the HTML or the application generating it (especially if you end up having to implement and test a special case just to do so). To that end, Selectors 4 now redefines :empty to include elements that contain only whitespace. This was originally proposed as a separate pseudo-class :blank but was recently retconned into :empty after it was determined that it was safe to do so without too many sites depending on the original behavior. Browsers will need to update their implementations of :empty in order to conform to Selectors 4. If you need to support older browsers, you will have to go through the hassle of marking elements containing only whitespace or pruning the whitespace before or after the fact.
#BoltClock provided a fantastic answer to this question, showing that this (currently, that is, working with CSS Specification 3) cannot be achieved by CSS alone.
#BoltClock mentioned that elements that are truly empty (which is a weird definition as explained) can be targeted by using the pseudo selector :empty. This pseudo selector is only available in CSS 3 and WILL NOT select elements that have only whitespace as content.
#BoltClock stated that the only way to clean up elements that have only whitespace as content is to fix the HTML, but that is not entirely correct. This can also be achieved through the implementation of Javascript.
KEEP IN MIND! The Javascript that I am offering to solve this issue may take a very long time to execute, so the best method is to clean up the raw HTML instead if possible. If that is not possible, then this may work as a solution, as long as you do not have too extensive of a DOM tree.
I'll walk through the steps of how to write the script yourself...
First of all, launch everything after page load.
This should be pretty obvious. You need to make sure that the DOM has fully loaded before running your script. Add an event listener for page load:
window.addEventListener("load", cleanUpMyDOM);
...and, of course, before that, create a function called cleanUpMyDOM. We will write the rest of our logic within this function.
Second, gather the elements that we are checking.
In our example we are going to check the entire DOM, but this is where our script can get VERY extensive and may make your page unresponsive. You may want to limit the amount of nodes you are iterating over.
We can grab the nodes in question by using the document.querySelectorAll. What's nice about this function is that it will level out the DOM tree and we won't have to recurse the children of each node.
var nodes = document.querySelectorAll("*");
As I said earlier, this code will grab EVERY DOM node, and that is probably NOT a good idea.
For example, I am working with WordPress, and some of the internal pages have some junk in them. Luckily, they are all p elements that are children of a div.body element, so I can change my selector to document.querySelectorAll("div.body p"), which will select only p elements that are children of my div.body element recursively. This will greatly optimize my script.
Third, iterate the nodes and find the empty ones.
We'll create a loop for the nodes array and check each node in it. We will then have to check to see if the node is empty. If it is empty, we'll apply a class to it called blank.
I'm just shooting from the hip here, so if you notice a mistake in this code, please let me know.
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
I am sure that there is a cleaner way to write the loop above, but this should get the job done.
Lastly, all you need to do is target the blank elements with your CSS.
Add this rule to your stylesheet:
.blank {
display:none;
}
And there you have it! All of your "blank" nodes have been hidden.
For those who just want to jump ahead, here is the finished script:
function cleanUpMyDOM(){
var nodes = document.querySelectorAll("*");
for(var i = 0; i < nodes.length; i++){
nodes[i].innerHTML = nodes[i].innerHTML.trim();
if(!nodes[i].innerHTML)
nodes[i].className += " blank";
}
}
window.addEventListener("load", cleanUpMyDOM);
Once again, if you notice any issues with my code, please let me know in the comments below.
Hope this helps!
P.S. Many people may be wondering why you would want to do this, as it does feel like bad practice. I would avoid doing this, but I am in a situation where I am starting to consider it. The content of the pages on my site are created through a WYSIWYG editor. This content is created and modified constantly by the marketing team and I get pretty overwhelmed handling the support for their slip-ups. Its not my job to fix WordPress's WYSIWYG editor (nor would I ever want to), but I could write a very simple script that can handle some of the work for me. That definitely seems like the better answer to me, besides training the support team on managing their whitespace when making edits.
For anyone looking at the exact link: https://drafts.csswg.org/selectors-4/Overview.bs#the-empty-pseudo
And TL;DR:
Note: In Level 2 and Level 3 of Selectors, '':empty'' did not match
elements that contained only white space. This was changed so that
that-- given white space is largely collapsible in HTML and is
therefore used for source code formatting, and especially because
elements with omitted end tags are likely to absorb such white space
into their DOM text contents-- elements which authors perceive of as
empty can be selected by this selector, as they expect.
And :empty will consider spaces as empty from v4 onwards:
content nodes (such as [[DOM]] text nodes, and entity references) whose data has a non-zero length must be considered as affecting emptiness; comments, processing instructions, and other nodes must not affect whether an element is considered empty or not.

Disable browser markup in contenteditable

I want to disable browsers from adding anything other than HTML tags in my contenteditable div.
Unequivocally, no ifs, ands, buts or candied nuts.
Right now, Chrome in particular is really [irritating] me.
If I have a style set, say
p { font-size:1.2em; line-height:1.6em; }
It will say "oh, you must mean you want:"
<span style="font-size:1.2em; line-height:1.6em;">stuff</span>
I most certainly do not. In fact, I don't want it creating ANY span tags - ever. If I do, I will explicitly say so in my javascript, and not a moment before.
This is a highly frustrating problem, and I know contenteditable is still one of those use at your own peril web features chocked full of bugs and near-sighted "oh we should do..." by the browser developers that really just suck for making nice clean HTML.
Is there anything I can do here? I've already tamed "paste from Word" to fix MIcrosofts bastardized code soup, conquered most of execCommands shortcomings, but this one.. this one.
I'll be [flustered] if I cannot work around the browsers injection of unwanted and unnecessary markup after coming this far, but I'm concerned. Is there any hope?
Here's a function that can be applied to a contenteditable element, and it will ensure that the element ends with only a single child node of nodeName "#text".
var sanitizeContentEditable(elem) {
var textContent = [];
for (var i = 0; i < elem.childNodes.length; i++) {
var child = elem.childNodes[i];
textContent.push(child.textContent);
if (child.nodeName !== '#text') textContent.push('\n');
}
elem.innerHTML = textContent.join('');
};
This function simply takes the text content of every child element, appends a newline to the contents of non-text child elements, and joins all such strings together, thereby ensuring that the contenteditable element has only a single, text-type child.
Concerning "I don't want it creating ANY span tags - ever", what about using an Advanced Content Filter configured so that it does not allow span tags at all? That might work. Or configuring it to not allow font-size and line-height styles for spans, if you secretly do want spans to appear sometimes.

Styling just comments inside a `pre` or `code` block with CSS

Is there a way to style comments inside a pre or code block (e.g. Ruby comments) using only CSS?
For example:
# I am a comment and should be lighter and italic
I = { :am => :normal_code, :and_want_no => :special_treatment }
I know you can use Javascript/jQuery to insert <span> elements in the right spots (like the <span>'s in the comment above provided by Stack Overflow) but can it be done with just CSS?
For background, I use a markdown renderer which outputs simple <pre> and <code> elements where necessary but without any hooks for indicating which language you're using and how to flag comments with <span> elements.
This task can't be done with just CSS.
CSS works at the element level and it is not possible to "select into" general text - even trivially, much less applying some rules to parse language grammar.
As noted, and as seen by inspecting the SO code rendering such as the one in this post, one approach is to output spans with the appropriate CSS classes (which are the result of separate grammar processing) - then these individual spans, which can selected, are styled.
a) What markdown renderer?
b) This can't be done with CSS with classes or ID's, as well as psuedo
elements
I will expand further as you do.
The problem is, you can't exactly render comments with your provided method, as these are technically never rendered in the first place.
comments are meant to be non-runnable code to help for debugging. Trying to add comments or manipulate comments would be a security breach and would require actually inserting a file into your appreciable code.
As far as that would go? That would be a tricky scenario unless you had the same comment or multiple files available to do so. I would say to just import your file if necessary with a duplicate version with a commented version.