Really simple: how do I most accurately test if a browser has support for a certain CSS selector?
I currently have some CSS code that makes the page a little more interactive by using the :checked selector in CSS, but I want to create a fallback script that does the same thing with JavaScript, but only if the user's browser has no support for the :checked selector.
My question is, how do I most accurately test if the user's browser supports a certain CSS selector?
Here is the code I'd like to use it on:
HTML:
<label class="coolbox">
<input type="checkbox"/>
<span>I want to eat some caek.</span>
</label>
CSS:
.coolbox input {display:none;}
.coolbox span::before {
content: "";
display:inline-block;
width:10px;
height:10px;
margin-right:5px;
border:1px solid black;
}
.coolbox:hover span::before {border:1px solid #555;}
.coolbox:active span::before {border:1px solid #999;}
.coolbox span::before {background-color:#F77;}
.coolbox input:checked + span::before {background-color:#4A4;}
Demo
PS: I'd prefer not to just use conditional comments, because I'd like to follow the standard of detecting features instead of browsers.
You could use querySelector:
function testSelector(selector, node){
var scope = document.createElement("div");
scope.appendChild(node);
try {
return scope.querySelector(selector) !== null;
} catch(e) { return false; }
}
You can test it like this:
var node = document.createElement("input");
node.type = 'checkbox';
node.checked = 'checked';
testSelector("input:checked", node); // === true
See this other question for more info on querySelector.
Workaround for your case:
<input type=checkbox checked=checked>
css:
input{
font-family:'arial';
}
input:checked{
font-family:'sans-serif';
}
checking procedure: js
alert($('input').css('font-family')=='sans-serif'?'supported':'not supported');
From some research I was able to find various websites that can test your browser to see the support.
This website is helpful to find what supports what but does not test your current browser.
CSS Selectors
This website will test your browser and if you don't want to use modernizr you can learn from their script.
CSS Test
This last website seems to do a great job and is very accurate of the support. I also am pretty sure I found the script that is doing this so like
I said you can learn from how other website are doing this.
CSS3Test
Source Code (add view-source: to view source, unable to link according to SO)
Support.js
To figure how they are making this work you will need to understand how their scripts are working. The bottom three seem to be the most critical to the function.
<script src="utopia.js"></script>
<script src="supports.js"></script>
<script src="csstest.js"></script>
<script src="tests.js"></script>
These are just some options that I found and it is up to your needs. Best of luck and hopefully this has been helpful.
A shorter way to do it would be to simply try the query selector, if it produces an error, return false, else true, like this:
function testSelector(selector) {
document.querySelector('*'); //checks if querySelector is implemented and raises an error if not
try {document.querySelector(selector)} catch (e) {return false}
return true;
}
I checked it on IE9 Windows and Chrome Mac (V43.0.2357.130), Win(V39.0.2171.95m), FireFox Win (V38.0.5), and it works fine with testSelector("form:invalid"), which is not implemented by IE9, but by everybody else.
While this is, admittedly, a very late answer to a rather old question it seemed worth adding another answer.
One approach, using JavaScript to test for selector support, is below with explanatory comments in the code:
// some DOM utilities and helpers,
// caching document as I don't enjoy typing that much:
const D = document,
// aliasing document.querySelector() and element.querySelector()
// again, because I don't enjoy typing; here the function takes
// two arguments 'sel' and 'context',
// 'sel' is a String, and is the selector for the element we're
// trying to retrieve;
// 'context' is the element/node we wish to search within; if
// no context is passed we default to document.querySelector()
// otherwise we use Element.querySelector():
get = (sel, context = D) => context.querySelector(sel),
// as above, except it's an alias for querySelectorAll(), and
// here we return an Array of nodes instead of a NodeList in
// order to use Array methods when/if required:
getAll = (sel, context = D) => [...context.querySelectorAll(sel)],
// alias for document.createElement(), which also allows properties
// to be set on the created element:
create = (tag, prop) => document.createElement(tag),
// simple function to allow for more elaborate templates, and
// arguments if required later:
templatedResult = (text) => `<code>${text}</code>`,
// named function to assess whether the browser supports/implements
// a given selector; this takes a reference to the Event Object
// passed automatically from EventTarget.addEventListener():
verifySelectorCompatibility = (evt) => {
// preventing default actions (as one of the events to which
// bind the function is form.submit):
evt.preventDefault();
// gathering variables/data
// here we retrieve the first/only <input> element within
// the document:
const input = get('#testSelector'),
// we retrieve the value from the <input> and trim that
// value of it's leading/trailing white-space:
selector = input.value.trim(),
// we retrieve the element with id=results:
output = get('#results'),
// we use the CSS.supports() function to assess whether
// the browser supports the supplied selector, which
// we pass to the function in a template string
// concatenating the selector within the string
// 'selector(...)' as required by the function:
result = CSS.supports(`selector(${selector})`),
// creating an <li>:
listElement = create('li');
// if the event-type is 'submit' and the user-entered selector
// - once trimmed of leading/trailing white-space - is zero-
// length we return at this point:
if (evt.type === 'submit' && selector.trim().length === 0) {
return false;
}
// here we add the class of 'supported' or 'unsupported' based on
// the 'result' variable being exactly equal to (Boolean) true
// or not:
listElement.classList.add(result === true ? 'supported' : 'unsupported');
// here we set the innerHTML of the <li> element to the template string
// from the defined function (above):
listElement.innerHTML = templatedResult(selector);
// we then use Element.prepend() to insert the new <li>
// as the first child of the 'output' element:
output.prepend(listElement);
},
// here we return the first/only <button> and <form> elements:
button = get('button'),
form = get('form');
// and we then bind the verifySelectorCompatibility() function
// to the 'click' event of the <button> and the 'submit' event
// of the <form>:
button.addEventListener('click', verifySelectorCompatibility);
form.addEventListener('submit', verifySelectorCompatibility);
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
form {
margin-block: 1em;
margin-inline: auto;
width: clamp(30rem, 60vw, 1000px);
}
fieldset {
display: grid;
gap: 1em;
grid-auto-rows: min-content;
grid-template-columns: repeat(4, 1fr);
padding-block: 0.25em;
padding-inline: 0.5em;
}
label {
display: flex;
flex-flow: row nowrap;
gap: 1em;
grid-column: 1 / -1;
margin-block: 1em;
margin-inline: 0.5em;
padding: 0.25em;
}
label span {
align-self: center;
}
label span::after {
content: ': ';
}
label input {
flex-grow: 1;
object-fit: cover;
padding: 0.25em;
}
button {
grid-column: -2 / span 2;
}
#results li {
border-block-end-width: 3px;
border-block-end-style: solid;
font-family: monospace;
font-size: 1.5em;
padding-block: 0.25em;
padding-inline: 0.5em;
}
.supported {
border-block-end-color: lime;
}
.supported::marker {
content: '\2713';
}
.unsupported {
border-block-end-color: red;
}
.unsupported::marker {
content: '\2717';
}
<form action="#" method="post">
<fieldset>
<legend>Does your browser support..?</legend>
<label>
<span>Enter a selector to test</span>
<input type="text" id="testSelector">
</label>
<button type="button">Check your browser</button>
</fieldset>
<ul id="results"></ul>
</form>
JS Fiddle demo.
References:
Arrow functions.
CSS.supports().
document.create().
document.querySelector().
document.querySelectorAll().
Element.classList API.
Element.prepend().
Element.querySelector().
Element.querySelectorAll().
Event.preventDefault().
EventTarget.addEventListener().
Template literals.
Related
I have used JavaScript to disable a button until all the inputs are filled, but I also want to keep the button disabled if any input is filled only with whitespace. (I am not using a form because it interferes with my other code.) How do I check if the inputs have any character other than whitespace?
JavaScript code that disables the button once the inputs are filled:
document.addEventListener('DOMContentLoaded', function(){
const required = document.querySelectorAll('.input');
//gets all the quiz_buttons
const quizButton = document.querySelectorAll('.quiz_button');
for (const i of required){
i.addEventListener("input", checkSubmit);
}
for (const button of quizButton){
button.disabled = true;
button.addEventListener('click', (event) =>
check_quiz(event.target.id));
}
function checkSubmit(){
let isValid = true;
for (const i of required){
isValid = isValid && !!i.value;
}
for (const button of quizButton){
button.disabled = !isValid;
}
}
});
You can use every to check if each input.value has length, and - using a regex - doesn't have just whitespace.
// Cache the elements up-front. We coerce `input` to
// and array so we can use `every` later
const container = document.querySelector('.container');
const inputs = [...document.querySelectorAll('input')];
const button = document.querySelector('button');
// Hang on listener off the parent component - using
// event delegation we can catch events from its child
// elements when they're fired and "bubble up" the DOM
container.addEventListener('input', handleInput);
function handleInput(e) {
// Check that the child element that fired the
// event is an input element
if (e.target.matches('input')) {
// Use `every` to iterate over the inputs.
// `every` returns a boolean - if the condition
// isn't met it returns false, otherwise true.
// Here the condition checks that the input has
// length, and isn't just all whitespace
let valid = inputs.every(input => {
return input.value.length
&& !/^\s+$/.test(input.value)
});
// Disable/enable the button based
// on the result
button.disabled = valid ? false : true;
}
}
<section class="container">
<input />
<input />
<input />
<input />
</section>
<button disabled>Submit</button>
Additional documentation
The regex reads:
^ - start of string
\s+ - one or more spaces
$ - end of string
test
Event delegation
This can be done in both CSS/HTML and with JavaScript; though the HTML and CSS approach does make use of the :has() pseudo-class (currently limited to Safari, Chrome and Edge (the latter two behind a flag)):
// I don't like typing, so I use these helpers to cache the
// document, and aliases for querySelector() and querySelectorAll():
const D = document,
get = (sel, context = D) => context.querySelector(sel),
getAll = (sel, context = D) => [...context.querySelectorAll(sel)];
// if the browser's CSS interface does not have support for the
// selector of ":has(a)":
if (!CSS.supports('selector(:has(a))')) {
// we retrieve all <label> elements:
const labels = getAll('label'),
// and all <input> elements:
inputs = getAll('input'),
// declare a function to check validity:
checkValidity = () => {
// we retrieve the <button> element:
const button = get('button'),
// we retrieve all <input> elements (this isn't strictly
// necessary, since we have a reference outside of the
// function and we could use that variable here, but
// this is my preference as it allows for code to be
// moved around:
inputs = getAll('input'),
// we check that every <input> is valid using the validityState
// interface; if so the state is set to (Boolean) true,
// otherwise it's set to false:
state = inputs.every((el) => el.validity.valid);
// here we invert the state (if all <input> elements are valid
// we want the disabled state of the <button> to be false):
button.disabled = !state;
};
// iterating over the <input> elements using Array.prototype.forEach():
inputs.forEach(
// arrow function passes the current <input> into the function
// as the 'el' argument; here we use EventTarget.addEventListener()
// to bind the checkValidity() function - note the deliberate lack
// of parentheses - as the event-handler for the 'input' event:
(el) => el.addEventListener('input', checkValidity)
);
}
/* A simple reset, to remove default margin and padding,
and to have element-sizes calculated by including
border-sizes and padding in the declared width: */
*,::before,::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
form {
/* using flex-box layout: */
display: flex;
/* flex-direction: row;
flex-wrap: wrap; */
flex-flow: row wrap;
/* setting a gap of 0.5em between adjacent elements: */
gap: 0.5em;
/* margin - in left-to-right, top-to-bottom languages
such as English - this is equivelent to margin-top
and margin-bottom: */
margin-block: 1em;
/* margin - in left-to-right, top-to-bottom languages
such as English - this is equivelent to margin-left
and margin-right: */
margin-inline: auto;
/* setting a preferred width of 60vw, but preventing
sizes smaller than 12em and limiting the maximum
to 1000px at most; inline-size is equivalent to
width (in English) */
inline-size: clamp(12em, 60vw, 1000px);
}
label {
/* vertically aligning the content */
align-items: center;
/* setting the border style:
border-width: 1px;
border-style: solid;
border-color: var(--color-validity);
this last sets the color of the border
to be equal to the CSS custom property:
*/
border: 1px solid var(--color-validity);
/* using flex-box layout on its contents: */
display: flex;
/* sizing the <label> to be 100% width: */
flex-basis: 100%;
/* setting its contents to
flex-direction: row;
flex-wrap: wrap;
to allow those contents to be displayed in rows,
and to be able to dynamically resize and wrap: */
flex-flow: row wrap;
/* allows the element to grow to occupy maximum space
possible: */
flex-grow: 1;
gap: inherit;
padding: 0.25em;
/* using a transition of 0.3seconds on the scale
property: */
transition: scale 0.3s ease-in;
}
label:focus-within {
/* increases the size of the element when the
input descendant is focused: */
scale: 1.1;
/* the element will be scaled from its center
point: */
transform-origin: 50% 50%;
}
/* a <label> that has a :valid descendant will
set the --color-validity property to lime: */
label:has(:valid) {
--color-validity: lime;
}
/* a <label> that has an :invalid descendant will
set the --color-validity property to red: */
label:has(:invalid) {
--color-validity: red;
}
/* styling the placeholder of the <input> elements */
::placeholder {
font-style: italic;
}
.titleText {
flex-basis: 30%;
flex-grow: 1;
white-space: nowrap;
}
/* an element with the class of "titleText" within a
<label> which has a descendant with the "required"
attribute will have the content of '*' in its
::after pseudo-element, and that will be colored
according the --color-validity custom-property: */
label:has([required]) .titleText::after {
content: '*';
color: var(--color-validity);
}
input {
flex-basis: 50%;
flex-grow: 1;
padding: 0.25em;
}
<form action="#">
<label>
<span class="titleText">Enter character first name</span>
<!-- to make use of HTML validation, both the 'required' attribute must be set and a pattern
supplied; the 'pattern' attribute is a string containing a regular expression,
below we have a regular expression to match:
1. ^ starts at the beginning of the string,
2. [A-Z] one upper-case letter in the range from A-Z inclusive, leading white-space
is not considered valid,
3. [a-zA-Z-] a letter of either upper or lower case between a-z, or a hyphen,
4. {2,} repeated at least twice (but with no maximum),
5. continuing until the end of the string (trailing white-space is not valid) -->
<input type="text" placeholder="John, Lucy, Rashda..." pattern="^[A-Z][a-zA-Z-]{2,}$" required>
</label>
<label>
<span class="titleText">Enter character family name</span>
<input type="text" placeholder="John, Lucy, Rashda..." pattern="^[a-zA-Z]'?[a-zA-Z]{2,}$" required>
</label>
<button type="submit">Submit</button>
</form>
JS Fiddle demo.
Note that while the CSS won't perform to style the <label> elements or their descendants, in browsers that don't support the :has() selector, the HTML validation of the <input> elements will still function if, for any reason, the JavaScript doesn't run.
References:
CSS:
:has().
HTML:
pattern attribute.
required attribute.
JavaScript:
Array.prototype.forEach().
CSS Interface.
CSS.supports().
document.
document.querySelector().
document.querySelectorAll().
Element.querySelector().
Element.querySelectorAll().
validityState Interface.
I am working on an API where the plan is that the users fill in a short checklist with multiple options before a POST request is made. For each section the users gets multiple options / buttons, but only 1 of them can be selected.
The selected button will get the class marked (Which changes the background color to green) whilst the others remain white (unless one of them is clicked, in which case it turns green and the others become white)
I have tried two different Javascript functions, but so far none of them were able to get this behavior to work.
Attempt 1:
function Label(self, group) {
// mark button
let btns = document.getElementsByClassName(group);
for (el in btns) {
btns[el].classList.remove('marked')
}
self.classList.add('marked');
}
Attempt 2 (a more explicit check to see if self is involved)
function Label(self, group) {
// mark button
let btns = document.getElementsByClassName(group);
for (el in btns) {
if (btns[el] !== self) {
btns[el].classList.remove('marked')
}
}
self.classList.add('marked');
}
My reasoning was to first remove the .marked class from all elements, and then set it on the this element. As the function is called with the onclick command in the HTML it knows which of the elements it is.
<div class="group1">
<button onclick="Label(this, pt_toggle)" class="pt_toggle">Incorrect</button>
<button onclick="Label(this, pt_toggle)" class="pt_toggle">Partially correct</button>
<button onclick="Label(this, pt_toggle)" class="pt_toggle">Correct</button>
</div>
However, the functions did not behave as I hoped. It throws an Uncaught TypeError: Cannot read property 'remove' of undefined error and the class is not set.
Does anyone know what I am doing wrong?
You're running into a feature of the for..in loop syntax. When you perform:
for (el in btns) {
if (btns[el] !== self) {
btns[el].classList.remove('marked');
}
}
every property in the btns object will be iterated over, including the length property which you won't be able to remove a class name from btns["length"].
If you switch to using a normal for loop using btns.length as the limit, you won't iterate over the non-button element properties in the object:
for (var el = 0; el < btns.length; el++) {
if (btns[el] !== self) {
btns[el].classList.remove('marked');
}
}
There is an element which performs this same kind of thing natively: the radio button. This makes it easier to code a more concise solution (well, except the CSS to make it look like more a button, but that's optional):
var buttons = Array.from(document.querySelectorAll(".group1 .button"));
buttons.forEach((el) => {
el.addEventListener("click", function (e) {
buttons.forEach((button) => button.classList.toggle("marked", button.querySelector("input").checked));
});
});
/* Hide the radio button */
.button input { display: none; }
/* make it green when selected */
.button.marked {
background: green
}
/* make the label look more like a button */
.button {
font: caption;
font-size: smaller;
padding: 2px 6px;
border: 1px solid #999;
border-radius: 1px;
background: white;
}
.button:hover {
border-color: ThreeDDarkShadow;
}
<div class="group1">
<label class="button"><input type="radio" name="pt_toggle">Incorrect</label>
<label class="button"><input type="radio" name="pt_toggle">Partially correct</label>
<label class="button"><input type="radio" name="pt_toggle">Correct</label>
</div>
Adding "marked" class on click to your button:
Your html:
<button onclick="Label(event)" class="pt_toggle">Incorrect</button>
Your js:
function Label(event) {
event.target.classList.add("marked");
}
-
There are a few posts on this already (like this one) but they're not really adaptable to this situation.
I have a shortcode that dynamically generates text content based on a custom field in my CRM, this is used to display a users serial number for their software, shown below:
The direct parent container for the serial number is #copyTarget2.
When the container is empty it is still rendered on the page but with no content, shown here:
I need to populate the empty container with 'placeholder' text, but not the kind that you can type over, just so that there is text visible that just reads - 'No active serial number.'
Lastly, when the field is populated, the placeholder text needs to be hidden, it doesn't really matter how it's hidden so long as it's not visible and doesn't affect the text generated by the shortcode.
I also tried to adapt this JS Fiddle: http://jsfiddle.net/davidThomas/LAfN2/
From:
$('input:text').each(
function(i,el) {
if (!el.value || el.value == '') {
el.placeholder = 'placeholdertext';
/* or:
el.placeholder = $('label[for=' + el.id + ']').text();
*/
}
});
To:
jQuery('#copyTarget2').each(
function(i,el) {
if (!el.value || el.value == '') {
el.placeholder = 'No valid serial number.';
/* or:
el.placeholder = $('label[for=' + el.id + ']').text();
*/
}
});
CSS and HTML are like second languages to me but I'm new to JS so I can modify code a little but I can't write it (yet), I'm wondering what the best JS solution would be to achieve this and why?
Using :empty and :before with attribute to set the placeholder when it is empty.
[data-placeholder]:empty:before {
color: #AAA;
content: attr(data-placeholder);
}
<span data-placeholder="My placeholder"></span>
<br/>
<span data-placeholder="My placeholder">I have text</span>
I think you need a effect like placeholder in the span element not the input tag .Try with some css .see the below snippet .And add the attribute using attr() of jquery function
see the CSS content
jQuery('#copyTarget2').each(
function(i, el) {
if (!$(el).text() || $(el).text().trim() == '') {
$(el).attr('placeholder', 'No valid serial number.')
/* or:
el.placeholder = $('label[for=' + el.id + ']').text();
*/
}
});
span:empty:before {
font-size: 12;
color: #9E9E9E;
line-height: 40px;
text-indent: 20px;
content: attr(placeholder);
display: block;
border: 1px solid grey;
}
span{
position:absolute;/*or fix as your wish with respect to parent element*/
width:200px;/*or fix as your wish with respect to parent element*/
height:50px;/*or fix as your wish with respect to parent element*/
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id="copyTarget2"></span>
CSS
p:hover { ... }
div { ... }
.something { ... }
#content a:hover { ... }
HTML
<div id="content">
<p>
Test
</p>
</div>
I need to select all elements, which have defined :hover subclass in CSS. For this example, it would be <p> and <a> elements.
Is it possible to do it in JavaScript ?
At first I didn't think it was possible, but after some thinking I came up with this. I wrote it with ES2015 syntax because a couple of things (like using forEach on non-arrays) is easier with it but it could be written in ES5 syntax too if needed.
let getElementsWithHoverRule = () => {
let getSelectors = rule => {
// get everything upto the first curly bracket
let selectorText = rule.cssText.match(/^[^{]+/)[0];
// in case a rule has multiple selectors,
// we will want to filter them separately
// so we don't capture elements that share
// styling but have different selectors
return selectorText.split(',');
},
selectors = [],
rxHover = /:hover/;
// loop through all the style sheets
[...document.styleSheets].forEach(sheet => {
// and all of the rules in those style sheets
let rules = sheet.cssRules || sheet.rules;
if (rules !== null) {
[...rules].forEach(rule => {
let ruleSelectors = getSelectors(rule);
selectors = selectors.concat(ruleSelectors);
});
}
});
// find all of the rules that contain hover
selectors = selectors.filter(selector => rxHover.test(selector));
// remove the :hover from the selectors so we can select them without the user
// hovering their mouse over them
selectors = selectors.map(selector => selector.replace(rxHover, ''))
return document.querySelectorAll(selectors.join(', '));
};
let hoverElement = getElementsWithHoverRule();
console.log(hoverElement);
// put red box around matched elements when the page is clicked
document.addEventListener('click', () => {
[...hoverElement].forEach(el => el.style.border = '5px outset #f00');
}, false);
p:hover { background: #eef }
span, a:hover { background: #000; color: #fff; }
div { color: #000; }
.something { color: #00f }
#content a:hover { color: #ff0 }
<div id="content">
<p>
Test non-link text
</p>
</div>
<p>another <span>paragraph</span>. A link that is not inside of content</p>
<br>
<br>
<br>
What it does is use document.styleSheets to get a list of all the style sheets and then loops through all the rules in them extracting their selectors. It then filters out the rules that don't contain :hover and then removes hover from the ones that do and uses those new rules to select the elements.
Edit
In the original code, if a rule had multiple selectors such as .foo, #bar:hover, it would return both .foo and #bar. I've updated the code so it will only return #bar since that is the only selector for the rule that contains :hover
There is a set of functions, the so-called "selectors api" https://developer.mozilla.org/en/docs/Web/API/Document/querySelector, https://developer.mozilla.org/en/docs/Web/API/Document/querySelectorAll and similar. But, nearly the only thing you can't do with these functions, is selecting pseudo classes (such as :hover).
I'm afraid you will have to monitor the mouseover and maybe mouseleave events and store the currently hovered element in a separate variable. By using its parentNode property (and its parent's parentNode property), you will have access to the parent chain.
I would suggest something like this:
var hoveredElement = null;
document.addEventListener( "mouseover", function(e){
hoveredElement = e.target;
});
Goal
In my program I want to do both things with jquery/javascript:
Change styling of css classes dynamically
Add/remove classes to elements
Problem
To do the first thing I use $(".className").css() method, but it changes style only for those elements that already have className class, i.e. if I later add className to an element its style won't be new. How can I solve this?
Example
See it also at jsfiddle.
$("p").addClass("redclass");
$(".redclass").css("color", "darkRed");
$("span").addClass("redclass");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>I want to be red! And I am.</p>
<span>I want to be red too but I'm not :'(</span>
Result:
A more shorten format:
$("<style/>", {text: ".redclass {color: darkRed;}"}).appendTo('head');
The snippet:
$("<style/>", {text: ".redclass {color: darkRed;}"}).appendTo('head');
$("p").addClass("redclass");
$("span").addClass("redclass");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>I want to be red! And I am.</p>
<span>I want to be red too but I'm not :'(</span>
While other (working) answers have been supplied, they don't actually answer your question - namely, they don't change the specified css class, but instead override it by adding another rule later in the document.
They achieve this, basically:
Before
.someClass
{
color: red;
}
After
.someClass
{
color: red;
}
.someClass
{
color: white;
}
When in many cases, a better option would see the color attribute of the existing rule altered.
Well, as it turns out - the browser maintains a collection of style-sheets, style-sheet rules and attributes of said rules. We may prefer instead, to find the existing rule and alter it. (We would certainly prefer a method that performed error checking over the one I present!)
The first console msg comes from the 1 instance of a #coords rule.
The next three come from the 3 instances of the .that rule
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
byId('goBtn').addEventListener('click', onGoBtnClicked, false);
}
function onGoBtnClicked(evt)
{
alterExistingCSSRuleAttrib('#coords', 'background-color', 'blue');
alterExistingCSSRuleAttrib('.that', 'color', 'red');
}
// useful for HtmlCollection, NodeList, String types (array-like types)
function forEach(array, callback, scope){for (var i=0,n=array.length; i<n; i++)callback.call(scope, array[i], i, array);} // passes back stuff we need
function alterExistingCSSRuleAttrib(selectorText, tgtAttribName, newValue)
{
var styleSheets = document.styleSheets;
forEach(styleSheets, styleSheetFunc);
function styleSheetFunc(CSSStyleSheet)
{
forEach(CSSStyleSheet.cssRules, cssRuleFunc);
}
function cssRuleFunc(rule)
{
if (selectorText.indexOf(rule.selectorText) != -1)
forEach(rule.style, cssRuleAttributeFunc);
function cssRuleAttributeFunc(attribName)
{
if (attribName == tgtAttribName)
{
rule.style[attribName] = newValue;
console.log('attribute replaced');
}
}
}
}
#coords
{
font-size: 0.75em;
width: 10em;
background-color: red;
}
.that
{
color: blue;
}
<style>.that{color: green;font-size: 3em;font-weight: bold;}</style>
<button id='goBtn'>Change css rules</button>
<div id='coords' class='that'>Test div</div>
<style>.that{color: blue;font-size: 2em;font-weight: bold;}</style>
#synthet1c has described the problem. My solution is:
$("head").append('<style></style>');
var element = $("head").children(':last');
element.html('.redclass{color: darkred;}');
What you are having issue with is that when you use the jQuery selector $('.redclass').css('color', 'darkRed') you are getting all the elements that currently have that class and using javascript to loop over the collection and set the style property.
You then set the class on the span after. Which was not included in the collection at the time of setting the color
You should set the class in your css file so it is distributed to all elements that have that class
console.log($('.redclass').length)
$("p").addClass("redclass");
console.log($('.redclass').length)
// $(".redclass").css("color", "darkRed");
$("span").addClass("redclass");
console.log($('.redclass').length)
.redclass {
color: darkRed;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>I want to be red! And I am.</p>
<span>I want to be red too but I'm not :'(</span>