How to change/remove CSS classes definitions at runtime? - javascript

I know it is possible to add new CSS classes definitions at runtime through JavaScript. But...
How to change/remove CSS classes definitions at runtime?
For instance, supose a I have the class below:
<style>
.menu { font-size: 12px; }
</style>
What I want is, at runtime, change the font-size rule of the .menu class, so that every element in the page who uses this class will be affected.
And, I also want to know how to remove the .menu class definition.

It's not difficult to change CSS rules at runtime, but apparently it is difficult to find the rule you want. PPK has a quick tour of this on quirksmode.org.
You'll want to use document.styleSheets[i].cssRules which is an array you need to parse through to find the one you want, and then rule.style.setProperty('font-size','10px',null);

I found an answer at http://twelvestone.com/forum_thread/view/31411 and I'm reproducing parts of the thread here, verbatim, because I'm afraid the thread, and the very helpful answer, will evaporate.
Flip 2006.06.26, 02:45PM —
[ Crunchy Frog ]
posts: 2470 join date: 2003.01.26
Well after about 10 to 12 hours of searching, reading, and tinkering I've done it! I am CSS/JS code Ninja today!
The JS code used is as follows:
<script language="JavaScript">
function changeRule(theNumber) {
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
} else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
theRules[theNumber].style.backgroundColor = '#FF0000';
}
</script>
I've tested this on FF(Mac), Safari(Mac), O9(Mac), IE5(Mac), IE6(PC), FF(PC) and they all work. The reason for the 'if' statement is some of the browsers use cssRules... some use just rules... And the only other hair is that you can't use "background-color" to refer to the style, you have to get rid of the hyphen and capitalize the first letter after the hyphen.
To refer to the first CSS rule you'd use "changeRule(0)", the second "changeRule(1)" and the third "changeRule(2)" and so on...
I haven't found a browser it doesn't work on.... yet....
Anything you say can and will be used against you. Over and over and over.
BillyBones 2011.01.20, 11:57AM —
[ in the barrel ]
posts: 1 join date: 2011.01.20
Hello, I registered in these forums just to add this little bit as I could not conveniently find it elsewhere:
function changeStyle(selectorText)
{
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
}
else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
for (n in theRules)
{
if (theRules[n].selectorText == selectorText) {
theRules[n].style.color = 'blue';
}
}
}
This simply makes the CSS rule identifiable by its selector name rather than by its index number in the cssRules array.
In other words, you can execute the Javascript function with the string argument "selectorText" instead of a number that is difficult to remember and susceptible to frequent changes if new styles are added.
Thank you for your 10 to 12 hours of research, Flip, I hope I made a worthy addition.

i think you are looking for this:
http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
this lets you change the actual rules with javascript. ive used it once, a few years ago it seemed to have worked.

I've made a simple helper function for anyone that want to do that:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
And then, you can use it like that:
getCSSRule('.mydiv').style.fontSize = '20px';
Take a look at the example below:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
document.querySelector('button').addEventListener('click', function(e) {
getCSSRule('.iframe').style.backgroundColor = 'orange';
});
.iframe {
height: 200px;
width: 200px;
display: inline-block;
border: 1px solid #000;
}
<p>
<button>Change .iframe background-color</button>
</p>
<div class="iframe"></div>
<div class="iframe"></div>

I took the best of the answers here, and combined them, for ease of use, and cross browser compatibility. Also, I covered when threw errors if no stylesheets were on the page, or if the css rule did not exist yet.
https://github.com/Frazer/dynamicallyAccessCSS.js

Here's an embarrassingly simple trick I've been using for dynamically manipulating CSS class rules, which dodges the complications (like parsing through the rules to locate the one you need, as noted by the expected answer), and even provides some extra flexibility for free.
(A side-note: the prev. attempt of adding this answer got instanty downvoted, within seconds, without feedback, by one of the few auto-notification targets of this page. I'm not sure the short knee-jerk reaction time was enough to see why this is a reliable solution that covers the OP's use case well, nevertheless I've rephrased it, guessing the narrative may have somehow been the trigger. Or the lack of semicolons (which I've now added for him). Or the lack of unnecessary curly braces (also added). Or the lack of notes on non-effects (added). Or not using a sufficiently complicated method (won't fix, even simplified the code yet some more). I'm sure he'll hit again anonymously, but still retry, because this is a clean & robust technique worth using.)
NOTE: This approach assumes you have sufficient control over the styling of the page to make sure the same class rule would not get created by a different method. (And it expects the HEAD element to exist.)
(One potential cost of this approach could be re-rendering on innerHtml, but since we are changing CSS anyway, repaint is imminent. Also, innerHtml is done to a non-displayed element, so browsers could optimize it out even if it otherwise mattered.)
OK. Since you can have any number of STYLE elements, it's perfectly fine to wrap your dynamic class in its own separate one. Um, that's it. :) Then, add an id or, even better*, a class attribute to the wrapper STYLE, so you can practically access and manipulate your class rule as if it was a DOM element. Just make sure it's wrapped in <style class="..."> ... </style>, when adding/replacing.
Bonus: you can also group multiple related rules and replace them all at once this way, if you wish (with trivial modifications):
function replace_class(classname, block) {
// Remove old:
var s = document.head.querySelector("style." + classname);
if (s) { document.head.removeChild(s); }
// Just delete?
if (!block) { return; }
// Add new:
s = document.createElement("style");
s.className = classname;
s.innerHTML = ("." + classname + block); // <style class="classname">.classname{...}</style>
document.head.appendChild(s);
}
Then, to update: replace_class("menu", "{font-size: 8px;}").
Or delete: replace_class("menu", null).
* Since CSS applies to every element, you may wonder why won't STYLE itself get unexpectedly rendered, if your new class had a display: ... with something else than none. Well, it would, if it was put in the BODY! But, since we add it to HEAD, rendering is skipped (unless, of course, you opt to display HEAD, too). Or, you could also use id instead, but then you had to invent/use proper scoping/prefixing, and why would you want that if it can be spared? (And I also like the subtle cheekiness of setting class when it's a class wrapper anyway...)

It is difficult to find the rule you want because you have to iterate through the document.styleSheets[i].cssRules array. (and compare your class name with the selectorText attribute)
So my solution to this problem is to add a new CSS class, remove the old CSS class from the HTML element and add this class instead of it.
var length = getCssRuleLength();
var newClassName = "css-class-name" + length;
//remove preview css class from html element.
$("#your-html-element").removeClass("css-class-name");
$("#your-html-element").removeClass("css-class-name" + (length-1));
$("#your-html-element").addClass(newClassName);
//insert a css class
insertCssRule("." + newClassName + ' { max-width: 100px; }', length);
function getCssRuleLength() {
var length = 0;
if (document.styleSheets[1].cssRules) {
length = document.styleSheets[1].cssRules.length;
} else if (document.styleSheets[1].rules) { //ie
length = document.styleSheets[1].rules.length;
}
return length;
}
function insertCssRule(rule, index) {
if (document.styleSheets[1].cssRules) {
document.styleSheets[1].insertRule(rule, index);
} else if (document.styleSheets[1].rules) { //ie
document.styleSheets[1].addRule(rule, index);
}
}

Below is an approach that will work for any given rule-selector and rule-change function:
// general function for selecting rules and applying changes
function change_css_rules(changeRuleFunc, selectorFunc) {
[].concat.apply([], // flattens arrays
Array.from(document.styleSheets).map(function(sheet) { // each ss
return Array.from(sheet.cssRules).filter(function(rule) { // each rule
return selectorFunc(rule); // only select desired rules
});
})
).map(changeRuleFunc); // change the selected rules
}
Example use:
var my_changeRuleFunc = function(rule) {
rule.style.fontSize = '20px';
}
var my_selectorFunc = function(rule) {
return rule.selectorText == '.myClass'; // return true to select this rule
}
change_css_rules(my_changeRuleFunc, my_selectorFunc); // apply change to selected rules

Related

Create a function to get selectors like jquery does with pure javascript

I am tired of including jquery in simple projects but I am so used to using it and I am trying to break free form my dependency on it. I am trying to create a function that will give the same feel of getting selectors like classes and tags. Example: $('selector').innerHTML = ".something";. I have just been looping through them one by one like so:
var classElements = document.querySelectorAll('.something');
for (var i = classElements.length - 1; i >= 0; i--) {
classElements[i].innerHTML = "This Is A Div";
}
But I wanted to create a function where I could just loop through a selector without having to write out a for loop for everything that I want to find. So I could just write it our like above $('.something').innerHTML = "something";
So far this is what I have but it will only get the first of each selector and won't get all of them. Needless to say I am very stuck and the more I read on the subject the more confused I get. I was wondering if someone could point me in the right direction where my thinking is flawed or explain how jquery goes about doing this. Here is my code:
window.getElements = function(selector) {
var selectors = document.querySelectorAll(selector);
for (var i = selectors.length - 1; i >= 0; i--) {
var elements = selectors[i];
}
return elements;
};
getElements(".something").innerHTML = "something";
Here is a fiddle Fiddle
Here is how you would do it. I have done what you have asked which is allow you to use all the native functionality rather than coin wrappers around it. jQuery returns its own api which acts on the selectors. What I have done is create a selector which allows you to act on each element it finds
window.getElements = function(selector,cb) {
var selectors = document.querySelectorAll(selector);
[].forEach.call(selectors, cb);
};
getElements(".something", function(el){el.innerHTML = "ha"});
getElements("#one", function(el){el.style.background = "red" });
It takes the dom list that is found, converts it into an array and then calls your passed function where you pass your native code
Here is a fiddle
https://jsfiddle.net/y52f4wh8/5/
Jquery works differently:
window.jquery = function(selector,cb) {
var selectors = document.querySelectorAll(selector);
function command(cb) {
[].forEach.call(selectors, cb);
};
// Here we return our own api that uses the command function to act on
// our selected list.
return {
html: function(str){
command(function(el){
el.innerHTML=str;
});
},
bg: function(color){
command(function(el){
el.style.background = color;
});
}
}
};
// The jquery way!
jquery(".something").html("ha");
getElements(".something").innerHTML = "something";
this does not work because that your function gets and returns all of the elements, which is a NodeList, but it does not automatically apply methods to every element in the collection as jQuery does. In order to do that, you would have to convert the elements to an array and actually call the function on each element using a loop or some other function.
EDIT: To be clear, you cannot just call NodeList.innerHTML = '' on a NodeList because innerHTML is applied to one element. jQuery internally takes care of BOTH of the collecting of elements, and the applying of methods for you.
EDIT #2: After examining your function more carefully, I have realized there are other issues, but what I wrote above is still the basis from which you want to spring.
You could use something like this for getting elements?:
function getElements(elements) {
return [...querySelectorAll(elements)]
}
But applying functions on nodes is going to be more selective on a case-by-case basis since many of them are applied differently.
The selector part of JQuery is called Sizzle. It has all the functionality that you need but does not come with the other parts of JQuery.
If you would like to find out more about the javascript behind it, I recommend to take a look at the sourcefiles of Sizzle.
jQuery is essentially a wrapper object for NodeList which adds more functionality to DOM operations. If you want to create your own wrapper object which defines functions for bulk versions of all the Element API, you are free to do so, but then you might as well use jQuery or some derivative.
If you want an extremely lightweight proxy object for doing bulk operations on DOM elements, there is the Proxy object in ES6 which can make this very easy to do, but has no IE support.
const $ = function(selector) {
const nodeList = document.querySelectorAll(selector);
return new Proxy(nodeList, {
set: function(target, property, value) {
for (let i = 0; i < target.length; i++) {
target[i][property] = value;
}
},
get: function(target, property) {
return target[0] && target[0][property];
}
});
};
console.log($('p').innerHTML);
$('p').innerHTML = 'Bulk assignement!';
<p>A B C</p>
<p>1 2 3</p>
<p>Do Re Mi</p>
An approach which would be best avoided is to define a setter for innerHTML on NodeList.
Object.defineProperty(NodeList.prototype, 'innerHTML', {
set(text) {
[...this].forEach(elt => elt.innerHTML = text);
}
});
const $ = selector => document.querySelectorAll(selector);
$('.foo').innerHTML = "it works";
<div class="foo"></div>
<div class="foo"></div>

Make less variables accessible from javascript?

I have a less file colours which defines looks like the following:
#black: #000;
#greyDarker: #222;
...etc
And want to be able to access these from within javascript, so for example:
$('body').style('color', 'window.colours.black') // or however I assign them
would work.
Since Less is getting compiled server-side the usual options wouldn't work.
I've started going ahead and writing a grunt task to generate a js file from these less rules however this seems like an inefficient / hacky way to go about it.
Any suggestions on a better approach or tools that could help
You could put some special definitions in your CSS to pass through the definitions. These would only be used to pass the variables and nothing else. You'd need to come up with some convention, here I've used div.less-rule-{rule-name}
For example.
div.less-rule-black {
background-color: #black;
}
div.less-rule-grey-darker {
background-color: #greyDarker;
}
You could then pick these up using the JavaScript API for accessing stylesheets. You could put this in a utility class somewhere. This only needs to be done once when all the stylesheets are loaded.
var rules, rule, i, n, j, m, key;
var lessRules = [];
for (i = 0, n = document.styleSheets.length; i < n; i++) {
rules = document.styleSheets[i].cssRules;
for (j = 0, m = rules.length; j < m; j++) {
rule = rules[j];
if (rules[j].selectorText.indexOf('less-rule') !== -1) {
key = /div.less-rule-(.*)/.exec(rules[j].selectorText)[1];
lessRules[key] = rule.style['background-color'];
}
}
}
You can then access the variables by using the key in the hash.
console.log(lessRules['black']);
console.log(lessRules['grey-darker']);
You can use css variables Like
let StyleVars = document.querySelector(':root');
// Create a function for getting a variable value
function GetStyleVars(Variable = "--color" ) {
// Get the styles (properties and values) for the root
var Style = getComputedStyle(StyleVars);
return(Style.getPropertyValue(Variable));
}
// Create a function for setting a variable value
function ChangeVariableValue(Value,Variable = "--color") {
// Set the value of variable to another Value
StyleVars.style.setProperty(Variable, Value);
}
document.getElementById("A").onclick = function(){
ChangeVariableValue('red');
}
document.getElementById("B").onclick = function(){
ChangeVariableValue('green');
}
document.getElementById("C").onclick = function(){
ChangeVariableValue('black');
}
document.getElementById("D").onclick = function(){
alert(GetStyleVars());
}
:root{
--color : #fff;
}
button{
border :2px solid var(--color);
}
<button id="A">Red</button>
<br>
<button id="B">Green</button>
<br>
<button id="C">Black</button>
<br>
<button id="D">Alert Value</button>
First add a rule using the LESS variable to your CSS.
Then create a dummy element with that class, and examine its getComputedStyle color:
function getLessVariableValue(variable) {
var elt = document.createElement('div');
elt.className = variable+"-variable";
elt.style.display= "none"; // ensure element is not visible
document.body.appendChild(elt); // elt must be in DOM to compute styles
var result = window.getComputedStyle(elt).fontFamily;
document.body.removeChild(elt);
return result;
}
document.writeln(getLessVariableValue('purple'))
/* replace "purple" below with '#purple' etc. */
.purple-variable { font-family: purple; }
We use font-family because it can take any arbitrary string as its value.
Alternatively, you could look through the style rules for the one with .purple-variable as a selector, as #Adam suggests in his answer. To me that seems a bit more involved.
However, this seems like a lot of work to go to, to accomplish--what? There may be better ways to accomplish what you are trying to do.
You can attach each color to a class name in your style rules and then add a particular class to your object to trigger the setting of the desired color. The class names would be defined in your CSS styles and then you just refer to them by string name in your javascript.
CSS:
.black {color: #000;}
.greyDarker {color: #222;}
Javascript:
$('body').addClass('black');
You can also use the CSS color names built into the browser:
$('body').css('color', 'black');
Here's a list of color names built in: http://www.crockford.com/wrrrld/color.html and http://www.cssportal.com/css3-color-names/,
Other than something like this, if you want programmatic access to symbolic names for colors, then you will need to generate your own Javascript definitions for the names and include that in your javascript where you can use those symbols.

Trying to make div background change color when 2 ids are true

I am trying to make each div's background change color when 2 ids exist. it is not changing the color. I cannot figure out what I am doing wrong. I am brand new to javascript. I have an embedded stylesheet and dont know if the javascript will override the css.
Also, I know some PHP and want to 'echo' the variables throughout the program so that I can see what the string value is in order to debug my own code. what is the easiest way to do this?
function drop(ev){
ev.preventDefault();
var image = ev.dataTransfer.getData("content");
ev.target.appendChild(document.getElementById(image));
var mydiv = '';
for (var i=0;i<9;i++)
{
if ($('#target'.i).find('#answer'.i).length == 1)
{
mydiv = document.getElementById('target'+i);
mydiv.style.backgroundColor = '#00CC00';
}
else
{
mydiv = document.getElementById('target'+i);
mydiv.style.backgroundColor = '#FF0000';
}
}
}
I think your problem may be on this line you have . not + to build the id's correctly.
if ($('#target'.i).find('#answer'.i).length == 1)
so your code should be:
if ($('#target'+i).find('#answer'+i).length == 1)
Keeping in mind I'm no jQuery wizard, my first notion was something like this:
$('div[id^=target]').each(function() {
var el = $(this).find('div[id^=answer]').addBack();
el.css('backgroundColor', el.length > 1 ? '#00CC00' : '#FF0000');
});
...but then I noticed that unlike your example, I was changing both the parent and child div. Something like this might be closer to your intent:
$('div[id^=target]').css('backgroundColor', function () {
return $(this).find('div[id^=answer]').length ? '#00CC00' : '#FF0000';
});
You also could retain the for loop if that's your preference:
for (var i = 0; i < 9; ++i) {
$('div#target' + i).css('backgroundColor', function() {
return $(this).find('div#answer' + i).length ? '#00CC00' : '#FF0000';
});
}
...and, just for fun, something kinda esoteric:
$('div[id^=target]:has(div[id^=answer])').css('backgroundColor', '#00CC00');
$('div[id^=target]:not(:has(div[id^=answer]))').css('backgroundColor', '#FF0000');
Fiddle!
Your code should work (see fiddle) with the correct operator for concatenation, i.e. with + instead of ., however here are a few points you should bear in mind :
Point 1 :
Among all the i variables you're iterating over in your for loop, if there is no div with id equal to "target" + i you will end up in the following else block :
else
{
mydiv = document.getElementById('target'+i); // null
mydiv.style.backgroundColor = '#FF0000';
}
At that place mydiv will be null and mydiv.style will throw an error.
Point 2 :
It seems you used jQuery to find the answers elements, while you used document.getElementById, which is part of the DOM API, to select then the target element. It would have been more consistent to use jQuery there too.
Point 3 :
If you want to simply output the value of some variable you can use console.log, which will output in the javascript console of the browser. The console object is provided by the browser, therefore you may not have the console.log method, but if you are using an up to date browser there is a good chance you will have it.
To summarize, see this fiddle for an example that takes these points into account.

using javascript to access position/font-color/font-size in a CSS class [duplicate]

I know it is possible to add new CSS classes definitions at runtime through JavaScript. But...
How to change/remove CSS classes definitions at runtime?
For instance, supose a I have the class below:
<style>
.menu { font-size: 12px; }
</style>
What I want is, at runtime, change the font-size rule of the .menu class, so that every element in the page who uses this class will be affected.
And, I also want to know how to remove the .menu class definition.
It's not difficult to change CSS rules at runtime, but apparently it is difficult to find the rule you want. PPK has a quick tour of this on quirksmode.org.
You'll want to use document.styleSheets[i].cssRules which is an array you need to parse through to find the one you want, and then rule.style.setProperty('font-size','10px',null);
I found an answer at http://twelvestone.com/forum_thread/view/31411 and I'm reproducing parts of the thread here, verbatim, because I'm afraid the thread, and the very helpful answer, will evaporate.
Flip 2006.06.26, 02:45PM —
[ Crunchy Frog ]
posts: 2470 join date: 2003.01.26
Well after about 10 to 12 hours of searching, reading, and tinkering I've done it! I am CSS/JS code Ninja today!
The JS code used is as follows:
<script language="JavaScript">
function changeRule(theNumber) {
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
} else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
theRules[theNumber].style.backgroundColor = '#FF0000';
}
</script>
I've tested this on FF(Mac), Safari(Mac), O9(Mac), IE5(Mac), IE6(PC), FF(PC) and they all work. The reason for the 'if' statement is some of the browsers use cssRules... some use just rules... And the only other hair is that you can't use "background-color" to refer to the style, you have to get rid of the hyphen and capitalize the first letter after the hyphen.
To refer to the first CSS rule you'd use "changeRule(0)", the second "changeRule(1)" and the third "changeRule(2)" and so on...
I haven't found a browser it doesn't work on.... yet....
Anything you say can and will be used against you. Over and over and over.
BillyBones 2011.01.20, 11:57AM —
[ in the barrel ]
posts: 1 join date: 2011.01.20
Hello, I registered in these forums just to add this little bit as I could not conveniently find it elsewhere:
function changeStyle(selectorText)
{
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
}
else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
for (n in theRules)
{
if (theRules[n].selectorText == selectorText) {
theRules[n].style.color = 'blue';
}
}
}
This simply makes the CSS rule identifiable by its selector name rather than by its index number in the cssRules array.
In other words, you can execute the Javascript function with the string argument "selectorText" instead of a number that is difficult to remember and susceptible to frequent changes if new styles are added.
Thank you for your 10 to 12 hours of research, Flip, I hope I made a worthy addition.
i think you are looking for this:
http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
this lets you change the actual rules with javascript. ive used it once, a few years ago it seemed to have worked.
I've made a simple helper function for anyone that want to do that:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
And then, you can use it like that:
getCSSRule('.mydiv').style.fontSize = '20px';
Take a look at the example below:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
document.querySelector('button').addEventListener('click', function(e) {
getCSSRule('.iframe').style.backgroundColor = 'orange';
});
.iframe {
height: 200px;
width: 200px;
display: inline-block;
border: 1px solid #000;
}
<p>
<button>Change .iframe background-color</button>
</p>
<div class="iframe"></div>
<div class="iframe"></div>
I took the best of the answers here, and combined them, for ease of use, and cross browser compatibility. Also, I covered when threw errors if no stylesheets were on the page, or if the css rule did not exist yet.
https://github.com/Frazer/dynamicallyAccessCSS.js
Here's an embarrassingly simple trick I've been using for dynamically manipulating CSS class rules, which dodges the complications (like parsing through the rules to locate the one you need, as noted by the expected answer), and even provides some extra flexibility for free.
(A side-note: the prev. attempt of adding this answer got instanty downvoted, within seconds, without feedback, by one of the few auto-notification targets of this page. I'm not sure the short knee-jerk reaction time was enough to see why this is a reliable solution that covers the OP's use case well, nevertheless I've rephrased it, guessing the narrative may have somehow been the trigger. Or the lack of semicolons (which I've now added for him). Or the lack of unnecessary curly braces (also added). Or the lack of notes on non-effects (added). Or not using a sufficiently complicated method (won't fix, even simplified the code yet some more). I'm sure he'll hit again anonymously, but still retry, because this is a clean & robust technique worth using.)
NOTE: This approach assumes you have sufficient control over the styling of the page to make sure the same class rule would not get created by a different method. (And it expects the HEAD element to exist.)
(One potential cost of this approach could be re-rendering on innerHtml, but since we are changing CSS anyway, repaint is imminent. Also, innerHtml is done to a non-displayed element, so browsers could optimize it out even if it otherwise mattered.)
OK. Since you can have any number of STYLE elements, it's perfectly fine to wrap your dynamic class in its own separate one. Um, that's it. :) Then, add an id or, even better*, a class attribute to the wrapper STYLE, so you can practically access and manipulate your class rule as if it was a DOM element. Just make sure it's wrapped in <style class="..."> ... </style>, when adding/replacing.
Bonus: you can also group multiple related rules and replace them all at once this way, if you wish (with trivial modifications):
function replace_class(classname, block) {
// Remove old:
var s = document.head.querySelector("style." + classname);
if (s) { document.head.removeChild(s); }
// Just delete?
if (!block) { return; }
// Add new:
s = document.createElement("style");
s.className = classname;
s.innerHTML = ("." + classname + block); // <style class="classname">.classname{...}</style>
document.head.appendChild(s);
}
Then, to update: replace_class("menu", "{font-size: 8px;}").
Or delete: replace_class("menu", null).
* Since CSS applies to every element, you may wonder why won't STYLE itself get unexpectedly rendered, if your new class had a display: ... with something else than none. Well, it would, if it was put in the BODY! But, since we add it to HEAD, rendering is skipped (unless, of course, you opt to display HEAD, too). Or, you could also use id instead, but then you had to invent/use proper scoping/prefixing, and why would you want that if it can be spared? (And I also like the subtle cheekiness of setting class when it's a class wrapper anyway...)
It is difficult to find the rule you want because you have to iterate through the document.styleSheets[i].cssRules array. (and compare your class name with the selectorText attribute)
So my solution to this problem is to add a new CSS class, remove the old CSS class from the HTML element and add this class instead of it.
var length = getCssRuleLength();
var newClassName = "css-class-name" + length;
//remove preview css class from html element.
$("#your-html-element").removeClass("css-class-name");
$("#your-html-element").removeClass("css-class-name" + (length-1));
$("#your-html-element").addClass(newClassName);
//insert a css class
insertCssRule("." + newClassName + ' { max-width: 100px; }', length);
function getCssRuleLength() {
var length = 0;
if (document.styleSheets[1].cssRules) {
length = document.styleSheets[1].cssRules.length;
} else if (document.styleSheets[1].rules) { //ie
length = document.styleSheets[1].rules.length;
}
return length;
}
function insertCssRule(rule, index) {
if (document.styleSheets[1].cssRules) {
document.styleSheets[1].insertRule(rule, index);
} else if (document.styleSheets[1].rules) { //ie
document.styleSheets[1].addRule(rule, index);
}
}
Below is an approach that will work for any given rule-selector and rule-change function:
// general function for selecting rules and applying changes
function change_css_rules(changeRuleFunc, selectorFunc) {
[].concat.apply([], // flattens arrays
Array.from(document.styleSheets).map(function(sheet) { // each ss
return Array.from(sheet.cssRules).filter(function(rule) { // each rule
return selectorFunc(rule); // only select desired rules
});
})
).map(changeRuleFunc); // change the selected rules
}
Example use:
var my_changeRuleFunc = function(rule) {
rule.style.fontSize = '20px';
}
var my_selectorFunc = function(rule) {
return rule.selectorText == '.myClass'; // return true to select this rule
}
change_css_rules(my_changeRuleFunc, my_selectorFunc); // apply change to selected rules

Get border value with getComputedStyle().getPropertyValue()? (Mozilla, FF)

In some browsers (namely, Firefox) the getComputedStyle().getPropertyValue() doesn't report anything for shorthand CSS, like border. Is there a non-specific-code way of getting these shorthand CSS values? I've considered making a whitelist of shorthand CSS and their respective longhand CSS values. But I realize doing that would be both a big pain and a non-forward-compatible design.
I'm wondering, what do you want to do with a string like border: 1px solid #000?
Say you want to reproduce an elems border in order to copy it copyStyle(el2, el, "border"):
// Copies a set of styles from one element to another.
function copyStyle(dest, source, shorthand) {
var computed = window.getComputedStyle(source, null);
for (var i = computed.length; i--;) {
var property = camelize(computed[i]);
if (property.indexOf(shorthand) > -1) {
console.log(property)
dest.style[property] = computed[property];
}
}
}
// prototype.js
function camelize(text) {
return text.replace(/-+(.)?/g, function (match, chr) {
return chr ? chr.toUpperCase() : '';
});
}
Comparing if two element's given set of styles matches can be done in the same manner. Other than that, I really can't see the use a string, which should be parsed if you want to compute anything with it.

Categories