:empty doesn't work if there's blank spaces? - css

Trying to find a pseudo class that'll target a <div> like this:
<div class="nav-previous">
</div>
I've tried :blank and :empty but neither can detect it. Is it just not possible to do?
https://jsfiddle.net/q3o1y74k/3/

:empty alone is enough.
By the current Selectors Level 4 specification, :empty can match elements that only contain text nodes that only contain whitespace as well as completely empty ones. It’s just there aren’t many that support it as per the current specification.
The :empty pseudo-class represents an element that has no children except, optionally, document white space characters.
From the MDN:
Note: In Selectors Level 4, the :empty pseudo-class was changed to act like :-moz-only-whitespace, but no browser currently supports this yet.
The :-moz-only-whitespace CSS pseudo-class matches elements that only contain text nodes that only contain whitespace. (This includes elements with empty text nodes and elements with no child nodes.)

As the others mentioned, this isn't possible with CSS yet.
You can check to see if there's only whitespace with JavaScript however. Here's a simple JS only solution, "empty" divs that match are blue, while divs that have text are red. Updated to add an empty class to the empty divs, which would allow you to target them easily with the selector .empty in your CSS.
The JS only "empty" comparison would look like this:
if(element.innerHTML.replace(/^\s*/, "").replace(/\s*$/, "") == "")
And if you're using jQuery it would be a bit easier:
if( $.trim( $(element).text() ) == "" ){
var navs = document.querySelectorAll(".nav-previous");
for( i=0; i < navs.length; i++ ){
if(navs[i].innerHTML.replace(/^\s*/, "").replace(/\s*$/, "") == "") {
navs[i].style.background = 'blue';
navs[i].classList.add( 'empty' );
} else {
navs[i].style.background = 'red';
}
}
.nav-previous {
padding: 10px;
border: 1px solid #000;
}
.nav-previous.empty {
border: 5px solid green;
}
<div class="nav-previous">
</div>
<div class="nav-previous">Not Empty </div>

The problem with your approach is that your container is not actually empty.
The :empty pseudo-class represents an element that has no children at
all. In terms of the document tree, only element nodes and content
nodes (such as DOM text nodes, CDATA nodes, and entity references)
whose data has a non-zero length must be considered as affecting
emptiness;
As you have empty spaces this pseudo class will not do the trick.
The :blank pseudo class should be the right one, because this is its definition:
This blank pseudo-class matches elements that only contain content
which consists of whitespace but are not empty.
the problem is that this pseudo class isn't implemented by any browser yet as you can check in the link below. So you will need to wait until it get implemented to be able to use this selector.
This pretty much explains the behavior you are facing
https://css4-selectors.com/selector/css4/blank-pseudo-class/
The best approach here is just to be sure that your div will actually be empty, so your approach will work.
the best that you can do is to define an empty class like this:
.empty{
display:none;
}
and then add this JS code here, it will append the empty class to your blank items:
(function($){
$.isBlank = function(html, obj){
return $.trim(html) === "" || obj.length == 0;
};
$('div').each(function() {
if($.isBlank(
$(this).html(),
$(this).contents().filter(function() {
return (this.nodeType !== Node.COMMENT_NODE);
})
)) {
$(this).addClass('empty');
}
});
})(jQuery);
check it working here,
https://jsfiddle.net/29eup5uw/

You just can't without JavaScript/jQuery implementation.
:empty selector works with empty tags (so without even any space in them) or with self-closing tags like <input />.
Reference: https://www.w3schools.com/cssref/css_selectors.asp
If you want to use JavaScript implementation, I guess here you will find the answer: How do I check if an HTML element is empty using jQuery?

:empty indeed only works for totally empty elements. Whitespace content means it is not empty, a single space or linebreak is already enough. Only HTML comments are considered to be 'no content'.
For more info see here: https://css-tricks.com/almanac/selectors/e/empty/
The :blank selector is in the works, it will match whitespace, see here: https://css-tricks.com/almanac/selectors/b/blank/. But it seems to have no browser support yet.
Update:
See here for possible solutions to this involving jQuery.

Related

Target elements without *any* attributes?

Is there a selector method that allows policing whether, eg. the HTML tag is a clean "<html>" with no attributes whatsoever?
I'm trying to create a Stylish override sheet for browser-generated image pages in Firefox, but I essentially have to apply it to all URIs since such pages are always still ostensibly from the images' own domains.
The easiest way seems to be testing whether the HTML and Body tags have zero attributes (plus only-child and class selection on the image tag) because the structure of most documents which haven't been generated by the browser won't start as simply as <html><body><img class="...
But all I can find is how to exclude a specific attribute, not all of them.
I've tried the following with no success:
[] {
color: blue;
}
[*] {
color: red;
}
<p>Clean element with no attributes</p>
<p class="has-class">Has class attribute</p>
<p id="has-id">Has ID attribute</p>
<p data-has-data-attribute="">Has data attribute</p>
The only remaining option I can come up with is just policing the standard attributes one would see ("class", "style", "name", "lang", etc.), but that's a lengthy and ever-changing list, notwithstanding the numerous non-standard ones.
A selector that matches a tag without attributes doesn't currently exist.
I read through the latest CSS selector reference and couldn't find a selector that does what you wish.
You can't use an asterisk in the attribute selector to select everything unfortunately. These two railroad diagrams represent what is allowed:
So your first two attempts are invalid, and the valid empty string seems to have no effect:
[] {
color: blue;
}
[*] {
color: red;
}
[""] { /* Seems to select nothing, rather than 'no attribute' */
color: magenta;
}
<p>Clean element with no attributes</p>
<p class="has-class">Has class attribute</p>
<p id="has-id">Has ID attribute</p>
<p data-has-data-attribute="">Has data attribute</p>
I can't come up with any possible hack that doesn't involve JavaScript. I will edit this answer if I figure out a solution.

Testing contents of after CSS selector in protractor

In my HTML I have element such as below
HTML:
<hmtl>
<head>
<style>
label::after {
content: " *"
}
</style>
</head>
<body>
<label> I'm mandatory</label>
</body>
</hmtl>
So what gets displayed on browser is:
I'm mandatory *
Query Selector
>getComputedStyle(document.querySelector('label')).content
<"normal"
So I see normal instead of *.
I can't see where is normal coming from. Is this the correct way to test content of ::after CSS selector?
I want to test that there's a "*" after the label, but can't seem to be able to get the value of "content" property correctly. Once I'm able to find it in using browser DOM API, I'd eventually want to test it in protractor.
Update
I found the answer at - Selenium WebDriver get text from CSS property "content" on a ::before pseudo element.
Now the question remains how I would test this on protractor.
Window.getComputedStyle()
The Window.getComputedStyle() method returns an object containing the values of all CSS properties of an element, after applying active stylesheets and resolving any basic computation those values may contain. Individual CSS property values are accessed through APIs provided by the object, or by indexing with CSS property names.
Syntax:
var style = window.getComputedStyle(element [, pseudoElt]);
element
The Element for which to get the computed style.
pseudoElt (Optional)
A string specifying the pseudo-element to match. Omitted (or null) for real elements.
The returned style is a live CSSStyleDeclaration object, which updates automatically when the element's styles are changed.
You can find a related discussion in WebDriver select element that has ::before
Usage with pseudo-elements
getComputedStyle() can pull style info from pseudo-elements (such as ::after, ::before, ::marker, ::line-marker.
As per the HTML, the <style> is as follows:
<style>
label::after {
content: " *"
}
</style>
Implemented as:
<label> I'm mandatory</label>
To retrieve you need to:
var label = document.querySelector('label');
var result = getComputedStyle(label, ':after').content;
console.log('the generated content is: ', result); // returns ' *'
Reference
CSS Pseudo-Elements Module Level 4
const label = document.querySelector('label'); // "normal";
console.log(label);
const labelAfter = getComputedStyle(label, ':after').content;
console.log(labelAfter == "normal");
label::after {
content: " *"
}
<label> I'm mandatory</label>
Since my question was specifically w.r.t protractor I'm posting the solution that I got working. Coming to the part I was stuck initially - why do I get "normal" instead of " *"
>getComputedStyle(document.querySelector('label')).content
<"normal"
So earlier I was unaware that ::after creates a pseudo child element inside the label element.
Inspecting <label> element in Chrome shows the below HTML
<label>
I'm mandatory
::after
</label>
If I click<label> element and checked the Computed tab, I could see that the value for content property is normal.
However, if I click on ::after pseudo-element, I can see in the Computed tab the value for content property is " *".
As mentioned in the other answers getComputedStyle() with the pseudo element as second parameter, is the only way to get value of CSS property for "::after". The crux of the problem is that protractor does not have an equivalent for getComputedStyle(), so we have to rely upon browser.executeScript() as shown below:
let labelHeader = 'I'm mandatory *';
// Passing label element separately as in the real test case, it would be extracted from parent
// enclosing element and need to be able to pass it as a parameter to browser.executeScript().
let label = element(by.css('label'));
browser.executeScript("return window.getComputedStyle(arguments[0], ':after').content",
label)
.then ((suffixData: string) => {
// suffixData comes out to be '" *"', double quotes as part of the string.
// So get rid of the first and last double quote character
suffixData = suffixData.slice(1, suffixData.length - 1);
labelText += suffixData;
expect(labelText).toBe(labelHeader);
});

Targetting element:only-child with no sibling text node [duplicate]

I would like to select anchor tags only when they're completely by themselves, that way I can make those look like buttons, without causing anchors within sentences to look like buttons. I don't want to add an extra class because this is going within a CMS.
I originally was trying this:
article p a:first-child:last-child {
background-color: #b83634;
color: white;
text-transform: uppercase;
font-weight: bold;
padding: 4px 24px;
}
But it doesn't work because text content isn't considered as criteria for :first-child or :last-child.
I would like to match
<p><a href='#'>Link</a></p>
but not
<p><a href='#'>Link</a> text content</p>
or
<p>text content <a href='#'>Link</a></p>
Is this possible with CSS?
The simple answer is: no, you can't.
As explained here, here and here, there is no CSS selector that applies to the text nodes.
If you could use jQuery, take a look at the contains selector.
Unfortunately no, you can't.
You have to use JS by it self or any librady of it to interact with content of elements and found where is each element in the content.
If you wish me to update my answer with a JS example prease ask for it.
I don't think it's generally possible, but you can come close. Here are some helpful places to start:
The Only Child Selector which would allow you to select all a elements which have no siblings like so a:only-child {/* css */}. See more here. (Also see edit)
The Not Selector which would allow you to exclude some elements perhaps using something along the lines of :not(p) > a {/* css */} which should select all anchors not in a paragraph. See some helpful information here.
Combining selectors to be as specific as possible. You might want all anchors not in an h1 and all anchors not in a p.
Example:
The final product might look like this:
a:only-child, :not(p) > a {/* css */}
This should select all anchors that are only children and anchors that are not in a paragraph.
Final note:
You may want to consider making the buttons actual button or input tags to make your life easier. Getting the HTML right first usually makes the CSS simpler.
Edit: the only child ignores the text, so that's pretty much useless here. I guess it's less doable than I thought.
jQuery Code Example:
// this will select '<p><a></a></p>' or '<p><a></a>text</p>'
// but not '<p><a></a><a></a></p>'
$('p').has('a:only-child').each(function() {
const p = $(this); // jQuerify
let hasalsotext = false;
p.contents().each(function(){
if ((this.nodeType === 3) && (this.nodeValue.trim() !== "")) {
hasalsotext = true;
return false; // break
}
});
if (!hasalsotext) {
$('a', p).addClass('looks-like-a-button');
}
});

using css empty to hide an element

Using chrome to inspect I see some code like this:
<div class="entry-content">
<p>We .... </p>
</div>
<footer class="entry-footer">
</footer>
Sometimes this footer is empty, and at other's it isn't.
When it is empty I try to hide it with:
footer.entry-footer:empty {
display:none;
}
but it doesn't work.
So I am either doing something wrong (or I guess it isn't really empty!)
:empty requires the element to be empty of whitespace too.
Here is an example, .test.blue and .test.red have white space and don't display: none; (without the JS below, where .test.red becomes hidden)...
if you want to remove the white space post load, here is some JS to do that:
var empties = document.querySelectorAll( '[selector_here]' );
for ( key in empties ) {
if ( typeof empties[key].innerHTML != "undefined" ) empties[key].innerHTML = empties[key].innerHTML.trim()
}
The JS above trims the the whitespace from any element matching the given selector, in my example I used the class empty (you can see it working on .test.red)
But i would recommend removing it from the HTML
I did a quick test for you and it would seem that white space counts as content. Beginning your closing tag on a new line will quietly insert a newline character, so which is why your selector for :empty fails.
As a solution, your html should be look like the following:
<footer class="entry-footer"></footer>
Because there's literally nothing between the start and end tags, the element passes as being :empty

Styling 'first-line' inner elements

I'm attempting to put CSS styles on the list items in the first line of a list but it seems that neither Chrome, Firefox, nor Safari will accept the style.
ul:first-line > li {
display: inline;
/* my styles here */
}
Have I overlooked the way in which I'm specifying the style, is this an oversight in CSS implementation or a deliberate CSS specification? If it is the latter, is there a good rationale behind this?
JSFiddle: http://jsfiddle.net/e3zzg/
Edit:
Please note, it seems pretty definitive that this can currently not be achieved using CSS alone but from a research standpoint and for posterity, I'm curious as to why this is. If you read the W3C CSS specification on the firstline pseudo-element there doesn't seem to be any mention of inner elements. Thanks to everyone trying to provide alternate solutions, but unless there actually is a CSS solution, the question here is 'why', not 'how' or 'is it possible'.
Here's "Why" What You Want to Do Cannot Be Done
The selectors 3 spec is a little more up to date. The following is taken from that.
The "why" is because the :first-letter is pseudo-element, that is, a "fake" or "false" element. It is producing a "fictional tag sequence", which is not recognizable in relation to other real elements. So your...
ul:first-line > li
...suffers from the same issues as this selector string...
ul:before + li
...where the combinator (whether > or +) is only looking at the "element" not the "pseudo-element" for selection. The second string does not target the "first" li of the ul that is following a :before pseudo-element. If it were to work at all, it would target an li that follows the ul in the html sequence (which, of course, there would never be one in a valid html layout).
However, a selector string similar to the second one above would not work anyway, because in actuality, the form of the above strings is not valid, as confirmed by the statement in the specifications that says:
Only one pseudo-element may appear per selector, and if present it
must appear after the sequence of simple selectors that represents the
subjects of the selector.
In other words, a pseudo-element can only be positioned dead last in the selector sequence, because it must be the target of the properties being assigned by that selector. Non valid forms apparently are simply ignored just like any invalid selector would be.
I think you would be better off with:
ul > li:first-child
:first-line is only useful for text elements
The only option to make a class apart for the second line is adding through Javascript a concrete className to them and setting the background for them. To get the current line you should iterate the elements and compare it's distance to the list top and it's previous siblings. I made a jQuery example so you can get the idea: http://jsfiddle.net/JmqxM/
$("ul.numerize-lines").each(function () {
var list = $(this);
var currentDistance = 0;
var currentLine = 0;
list.find("li").each(function () {
var item = $(this);
var offset = .offset();
var topDistance = offset.top;
if (topDistance > currentDistance) {
currentDistance = topDistance;
currentLine += 1;
}
item.addClass("line-" + currentLine);
});
});
and the css:
ul li.line-2{
background-color: #FFF;
}
Pretty sure the :first-line should be applied to the element itself that contains the text (rather than the parent, as you have).
ul > li:first-line { /*style*/ }
Or if your list items contain tags or something else like that...
ul > li p:first-line { /*style*/ }

Resources