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

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');
}
});

Related

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

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.

change color from text except everything is between tags <b>

How to change the color only from text except everything is between tags ?
Sample text:
<b>A7</b> <b>D</b>
this is a test
<b>A7+</b> <b>G9</b>
this is a test
Assuming that all of that text is wrapped in a parent element (I've used <div>, but almost any other element would suffice), as such:
<div>
<b>A7</b>
<b>D</b>
this is a test
<b>A7+</b>
<b>G9</b>
this is a test
</div>
Then you can't change "all the text except the <b> tags", because CSS won't allow you to style the text without affecting the colour of the the <b> elements, you can, however, style the div and then 'unstyle' the b elements:
div {
color: #f00;
}
div b {
color: #000;
}
JS Fiddle demo.
To do this with jQuery (and, honestly, from the information you've posted jQuery seems unnecessary), you'd have to create wrapping elements for each of the strings of characters that are not wrapped in b elements and then directly style, or add a class to, those elements:
$('body').contents().filter(function(){
return this.nodeType === 3 && this.nodeValue.trim().length > 0;
}).wrap('<span />').parent().css('color','red');
JS Fiddle demo.
References:
contents().
filter().
parent().
wrap().
Try:
body{color:red;}
b{color:black;}
Fiddle here.
You could use jQuery like this:
$('body').css('color', '#FFCCFF');
$('b').css('color', '#000000');
But if you can do it in CSS it would be better:
body {
color: #FFCCFF;
}
b {
#000000;
}
Since you tagged this as jquery, I just provided a solution for this with jquery, You may wrap the html which was written by you in a paragraph tag like below. And then you have to use the .addClass function of Jquery to set different classes with different colours for that both paragraph and bold tag.
HTML
<p><b>A7</b><b>D</b>
this is a test
<b>A7+</b><b>G9</b>
this is a test</p>
CSS
.Paragraph{
color:red;
}
.boldtext{
color:black;
}
JQUERY
$('p').addClass('Paragraph');
$('p > b').addClass("boldtext");
DEMONSTRATION

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*/ }

Select all sibling elements, not just following ones

The intent is to target all the other elements of the same type & same level whenever one is hovered. Tried
a:hover ~ a
Only to notice that this doesn't target the elements before the hovered one... Is there a solution with css? Or should I just somehow js my way out of this
This is a variation on the parent or < selector question (of which there are many). I know it's not quite the same, but I'm sure you can imagine how a sibling selector would be derived from a parent selector.
Jonathan Snook has an excellent blog post on why this doesn't exist, and I don't think I can do any better, so I'll leave you to read that if it interests you. Basically, it's a technically difficult job because of the way elements are selected, and it would lead to a whole world of mess in terms of code structure.
So the short answer is, this doesn't exist and you'll need to resort to JS to fix it, I'm afraid.
Edit: Just a couple of examples of fixes. Using jQuery:
$(selector).siblings().css({...});
or if you want to include the element:
$(selector).parent().children().css({...});
Or in vanilla JS:
var element = document.querySelectorAll(selector); // or getElementById or whatever
var siblings = element.parentNode.childNodes;
for (var i = 0; i < siblings.length; i++) {
if (siblings[i] !== element) { // optional
siblings[i].style.color = 'red';
}
}
You can do this by using jQuery to toggle the hover states instead of CSS:
HTML:
​<div>
Link 1
Link 2
Link 3
</div>​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
CSS:
div a {color: #000;}
div a.hover {color: #00f;}
​
jQuery:
$("div a").hover(
function(){
$("div a").addClass("hover");
},
function(){
$("div a").removeClass("hover");
}
);
Fiddle

CSS text-transform capitalize on all caps

Here is my HTML:
small caps &
ALL CAPS
Here is my CSS:
.link {text-transform: capitalize;}
The output is:
Small Caps & ALL CAPS
and I want the output to be:
Small Caps & All Caps
Any ideas?
You can almost do it with:
.link {
text-transform: lowercase;
}
.link:first-letter,
.link:first-line {
text-transform: uppercase;
}
It will give you the output:
Small Caps
All Caps
There is no way to do this with CSS, you could use PHP or Javascript for this.
PHP example:
$text = "ALL CAPS";
$text = ucwords(strtolower($text)); // All Caps
jQuery example (it's a plugin now!):
// Uppercase every first letter of a word
jQuery.fn.ucwords = function() {
return this.each(function(){
var val = $(this).text(), newVal = '';
val = val.split(' ');
for(var c=0; c < val.length; c++) {
newVal += val[c].substring(0,1).toUpperCase() + val[c].substring(1,val[c].length) + (c+1==val.length ? '' : ' ');
}
$(this).text(newVal);
});
}
$('a.link').ucwords();​
Convert with JavaScript using .toLowerCase() and capitalize would do the rest.
Interesting question!
capitalize transforms every first letter of a word to uppercase, but it does not transform the other letters to lowercase. Not even the :first-letter pseudo-class will cut it (because it applies to the first letter of each element, not each word), and I can't see a way of combining lowercase and capitalize to get the desired outcome.
So as far as I can see, this is indeed impossible to do with CSS.
#Harmen shows good-looking PHP and jQuery workarounds in his answer.
I'd like to sugest a pure CSS solution that is more useful than the first letter solution presented but is also very similar.
.link {
text-transform: lowercase;
display: inline-block;
}
.link::first-line {
text-transform: capitalize;
}
<div class="link">HELLO WORLD!</div>
<p class="link">HELLO WORLD!</p>
HELLO WORLD! ( now working! )
Although this is limited to the first line it may be useful for more use cases than the first letter solution since it applies capitalization to the whole line and not only the first word. (all words in the first line)
In the OP's specific case this could have solved it.
Notes: As mentioned in the first letter solution comments, the order of the CSS rules is important! Also note that I changed the <a> tag for a <div> tag because for some reason the pseudo-element ::first-line doesn't work with <a> tags natively but either <div> or <p> are fine.
EDIT: the <a> element will work if display: inline-block; is added to the .link class. Thanks to Dave Land for spotting that!
New Note: if the text wraps it will loose the capitalization because it is now in fact on the second line (first line is still ok).
JavaScript:
var links = document.getElementsByClassName("link");
for (var i = 0; i < links.length; i++) {
links[i].innerHTML = links[i].innerHTML.toLowerCase();
}
CSS:
.link { text-transform: capitalize; }
What Khan "ended up doing" (which is cleaner and worked for me) is down in the comments of the post marked as the answer.
captialize only effects the first letter of the word. http://www.w3.org/TR/CSS21/text.html#propdef-text-transform
You can do it with css first-letter!
eg I wanted it for the Menu:
a {display:inline-block; text-transorm:uppercase;}
a::first-letter {font-size:50px;}
It only runs with block elements - therefore the inline-block!
May be useful for java and jstl.
Initialize variable with localized message.
After that it is possible to use it in jstl toLowerCase function.
Transform with CSS.
In JSP
1.
<fmt:message key="some.key" var="item"/>
2.
<div class="content">
${fn:toLowerCase(item)}
</div>
In CSS
3.
.content {
text-transform:capitalize;
}
If the data is coming from a database, as in my case, you can lower it before sending it to a select list/drop down list. Shame you can't do it in CSS.
After researching a lot I found jquery function/expression to change text in first letter in uppercase only, I modify that code accordingly to make it workable for input field. When you will write something in input field and then move to another filed or element, the text of that field will change with 1st-letter capitalization only. No matter user type text in complete lower or upper case capitalization:
Follow this code:
Step-1: Call jquery library in html head:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
Step-2: Write code to change text of input fields:
<script>
$(document).ready(function(){
$("#edit-submitted-first-name,#edit-submitted-last-name,#edit-submitted-company-name, #edit-submitted-city").focusout(function(){
var str=$(this).val();
str = str.toLowerCase().replace(/\b[a-z]/g, function(letter) {
return letter.toUpperCase();
});
$(this).val(str);
});});
</script>
Step-3: Create HTML input fields with same id's you use in jquery code like:
<input type="text" id="edit-submitted-first-name" name="field name">
The id of this input field is: edit-submitted-first-name (It using in jquery code in step-2)
**Result:
Make sure the text will change after you move your focus from that input field at another element. Because we using focus out event of jquery here.
Result should like this: User Type: "thank you" it will change with "Thank You".
**
Best of luck
The PHP solution, in backend:
$string = 'UPPERCASE';
$lowercase = strtolower($string);
echo ucwords($lowercase);
I know this is a late response but if you want to compare the performance of various solutions I have a jsPerf that I created.
Regex solutions are the fastest for sure.
Here is the jsPerf: https://jsperf.com/capitalize-jwaz
There are 2 regex solutions.
The first one uses/\b[a-z]/g. Word boundary will capital words such as non-disclosure to Non-Disclosure.
If you only want to capitalize letters that are preceded by a space then use the second regex
/(^[a-z]|\s[a-z])/g
if you are using jQuery; this is one a way to do it:
$('.link').each(function() {
$(this).css('text-transform','capitalize').text($(this).text().toLowerCase());
});
Here is an easier to read version doing the same thing:
//Iterate all the elements in jQuery object
$('.link').each(function() {
//get text from element and make it lower-case
var string = $(this).text().toLowerCase();
//set element text to the new string that is lower-case
$(this).text(string);
//set the css to capitalize
$(this).css('text-transform','capitalize');
});
Demo
all wrong it does exist --> font-variant: small-caps;
text-transform:capitalize; just the first letter cap

Resources