Can I programmatically traverse a CSS stylesheet? - javascript

jQuery provides a nice, neat way to traverse the DOM...what I'm looking for is a way to traverse a stylesheet, getting and setting attributes for the defined styles.
Example Stylesheet
div {
background: #FF0000;
display: block;
}
.style {
color: #00AA00;
font-family: Verdana;
}
html body > nav.menu {
list-style: none;
}
Now imagine the following code is like jQuery for CSS...
Getting values from the CSS
$("div").attr("background");
//returns #FF0000;
$(".style").attr("color");
// returns #00AA00;
$("html body > nav.menu").attr("color");
// returns undefined;
Setting values in the CSS
$("div").attr("background", "#0000FF");
$(".style").attr("color", "#CDFF4E");
$("html body > nav.menu").attr("color", "#FFFFFF");
Fairly certain this is not possible...but just a wild stab in the dark!

I think you can, but the interface is more obtuse than you probably want.
document.styleSheets returns a StyleSheetList object that seems to behave in an array like way.
So document.styleSheets[0] returns a CSSStyleSheet object. Look to have lots of ways to analyze it's content. And each CSSStyleSheet has a cssRules property which returns a CSSRuleList.
And you can traverse the docs on the various types return by the DOM api from there yourself: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet

I just found a way to look through all of your style sheets, using jquery initially:
I have three stylesheets on my page, so first, I must identify the one I need to manipulate and I gave it an id:
<style id="localRules">...</style>
Then, I use jQuery to initially find the id'd stylesheet I'm planning to change:
var sheetToChange = "localRules";
var sheets = $(document.styleSheets);
// loop through all the stylesheets
for (var thisSheet=0;thisSheet<sheets.length;thisSheet++){
// find the right stylesheet to work on
if(sheets[thisSheet].ownerNode.id == sheetToChange ){
// cross browser referencing of css rules:
var ruleSet = sheets[thisSheet].cssRules || sheets[thisSheet].rules;
for (var thisRule=0;thisRule<ruleSet.length;thisRule++){
// traverse in that style sheet for the rule you want to change, in this case, body:
if(ruleSet[thisRule].selectorText == "body"){
ruleSet[thisRule].style.cursor = "pointer";
}
}
break;
}
}
Hope this is helpful...it worked for me, but took a while to figure it out, especially because ownerNode is something I've never heard of before.

Related

Is it possible to calculate (compute) resulting css style manually?

Is it possible to compute resulting css style on the element manually (without need to render it)?
Lets say I'm supposed to have an HTML structure:
<p style="some_style1">
<span style="some_style2">
<span style="some_style3">
TEXT
</span>
</span>
</p>
I know what are some_style1, some_style2, some_style3 in terms of JS object (for example i have data for each element like: {font: 'Times New Roman' 12px bold; text-align: center;})
I want to MANUALLY (without need to render in browser the whole structure) compute resulting style that will effect "TEXT".
What algorithm (or solution) should I use?
There exist browsers that don't need rendering in a window (headless browser). You can load a page and query what you want. It won't be easier than in a normal browser to obtain what you ask though.
JSCSSP is a CSS parser written in cross-browser JavaScript that could be a first step to achieve what you want from scratch or quite. Give it a stylesheet and it'll tell you what a browser would've parsed. You still must manage:
the DOM,
inheritance of styles,
determine which rules apply to a given element with or without class, id, attributes, siblings, etc
priorities of selectors
etc
Its author is D. Glazman, co-chairman of the W3C CSS group and developer of Kompozer, NVu and BlueGriffon so it should parse CSS as expected :)
The simplest thing I can think of is to wrap the whole thing in a a container that you set display: none on, and append it to the DOM. The browser won't render it, but you'll then be able to query the computed style.
Here's an example showing how jQuery can't find the style information when the structure isn't connected to the DOM, but when it is, it can:
jQuery(function($) {
// Disconnected structure
var x = $("<p style='color: red'><span style='padding: 2em'><span style='background-color: white'>TEXT</span></span></p>");
// Get the span
var y = x.find("span span");
// Show its computed color; will be blank
display("y.css('color'): " + y.css('color'));
// Create a hidden div and append the structure
var d = $("<div>");
d.hide();
d.append(x);
d.appendTo(document.body);
// Show the computed color now; show red
display("y.css('color'): " + y.css('color'));
// Detach it again
d.detach();
function display(msg) {
$("<p>").html(String(msg)).appendTo(document.body);
}
});
Live copy | source
I can't guarantee all values will be exactly right, you'll have to try it and see; browsers may defer calculating some things until/unless the container is visible. If you find that some properties you want aren't calculated yet, you may have to make the div visible, but off-page (position: absolute; left: -10000px);
I found some articles about this: Can jQuery get all styles applied to an element on Stackoverflow.
Also this one on quirksmode: Get Styles that shows the following function:
function getStyle(el,styleProp)
{
var x = document.getElementById(el);
if (x.currentStyle)
var y = x.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
return y;
}
This allows you to query for style properties
Styles override each other in the order in which they're defined: So anything in some_style3 that overrides the same selector in some_style2, say, will do. Otherwise, it will just be a union of the sets of selectors.
EDIT Some selectors won't override, but instead act relatively on a previous definition, so you've got to be careful about that.

Javascript, CSS: Get element by style attribute

I'd like to:
Find a style attribute for all elements in the page (for instance: all elements that have color:#333;)
Change this attribute for all of them (for instance from color:#333 to color:#444).
Do you have any suggestion on doing so?
My suggestion is avoid doing this if at all remotely possible. Instead, use a class to assign the color value, and then you can look up the elements using the class, rather than the color value.
As far as I'm aware, there's no selector (not even in CSS3) that you can use to query a specific style value, which means looping through all elements (or it looks like you can restrict it to all elements with a style attribute) and looking at the element.style.color property. Now, the thing is, even though you write color: #333; in your style attribute, different browsers will echo it back to you in different ways. It might be #333, it might be #333333, it might be rgb(51, 51, 51), it might even be rgba(51, 51, 51, 0).
So on the whole, a very awkward exercise indeed.
Since you've said this is for a Chrome extension, you probably don't have to worry as much about multiple formats, although I'd throw in the ones that we've seen in the wild in case Chrome changes the format (perhaps to be consistent with some other browser, which has been known to happen).
But for instance:
(function() {
// Get all elements that have a style attribute
var elms = document.querySelectorAll("*[style]");
// Loop through them
Array.prototype.forEach.call(elms, function(elm) {
// Get the color value
var clr = elm.style.color || "";
// Remove all whitespace, make it all lower case
clr = clr.replace(/\s/g, "").toLowerCase();
// Switch on the possible values we know of
switch (clr) {
case "#333":
case "#333333":
case "rgb(51,51,51)": // <=== This is the one Chrome seems to use
case "rgba(51,51,51,0)":
elm.style.color = "#444";
break;
}
});
})();
Live example using red for clarity | source - Note that the example relies on ES5 features and querySelectorAll, but as this is Chrome, I know they're there.
Note that the above assumes inline style, because you talked about the style attribute. If you mean computed style, then there's nothing for it but to loop through all elements on the page calling getComputedStyle. Other than that, the above applies.
Final note: If you really meant a style attribute with precisely the value color: #333 and not the value color:#333 or color:#333333; or color: #333; font-weight: bold or any other string, your querySelectorAll could handle that: querySelectorAll('*[style="color: #333"]'). But it would be very fragile.
From your comment below, it sounds like you're having to go through every element. If so, I wouldn't use querySelectorAll at all, I'd use recursive descent:
function walk(elm) {
var node;
// ...handle this element's `style` or `getComputedStyle`...
// Handle child elements
for (node = elm.firstChild; node; node = node.nextSibling) {
if (node.nodeType === 1) { // 1 == Element
walk(node);
}
}
}
// Kick it off starting with the `body` element
walk(document.body);
That way you don't build up large, unnecessary temporary structures. This is probably the most efficient way to walk the entire DOM of a document.
It's definitely more simple if you use jquery.
In any case, the best would be to use classes and use the filter jquery method to get the objects you want.
But if you really want to get them you can do something like:
$(function () {
$('p').filter(function () {
return $(this).css('color') == '#333';
}).css('color', '#444');
});
The above script get the elements with the desired css attribute and set a new css attribute (color #444).
You can't, if you don't add at least a specific CSS class to all this elements you want to track.
Or better, you can with very poor performances by looping on all the elements of the DOM until you find what you're looking for. But please, don't think of doing this
It's as already said really hard / inefficient to query all elements by color.
// refrence: http://stackoverflow.com/questions/5999209/jquery-how-to-get-the-background-color-code-of-an-element
var arr = [];
$('*').each(function (i, ele) {
// is red => save
if($(ele).css('backgroundColor') == ('rgb(0, 0, 255)')) arr.push(ele);
});
console.log(arr);
Here is an JSFiddle Example for it: http://jsfiddle.net/ddAg7/
My recommendation for this is: Don't do it!
Something like
$('selector').each(function() {
if($(this).attr('style').indexOf('font-weight') > -1) {
alert('got my attribute');
}
});
in the if statement you could replace it with a different css... Not sure.. haven't tried on all browsers though :)
oneliner
[...document.querySelectorAll("[style*='color:#333']")].forEach(e=>e.style.color='#444')
function switchColors() {
[...document.querySelectorAll("div[style*='color:#333']")]
.forEach(el => el.style.color = '#f00')
}
<div>EXAMPLE:
<div>
<div style="background: #eee; color:#333;">aaa</div>
<div style="background: gray; color:#00f;">
bbb
<div style="color:#333;">ccc</div>
</div>
</div>
<div style="color:#040;">ddd</div>
<div style="color:#333; font-size: 20px">eee</div>
</div>
<button onclick="switchColors()">Switch Colors</button>

How to dynamically set and modify CSS in JavaScript?

I have some JavaScript code that creates some div elements and it sets their CSS properties.
Because I would like to decouple CSS logic from my JavaScript code and because CSS is easier to read in its own .css file, I would like to set the CSS className of my element and then dynamically inject some values into the defined CSS property.
Here is what I would like to do :
style.css:
.myClass {
width: $insertedFromJS
}
script.js:
var myElement = document.createElement("div");
myElement.className = "myClass";
I want to do something like this but at that point myElement.style.width is empty
myElement.style.width.replaceAll("$insertedFromJS", "400px");
I think my problem here is that after the call to myElement.className = "myClass", the CSS is not yet applied.
If I understand your question properly, it sounds like you're trying to set placeholder text in your css file, and then use javascript to parse out the text with the css value you want to set for that class. You can't do that in the way you're trying to do it. In order to do that, you'd have to grab the content of the CSS file out of the dom, manipulate the text, and then save it back to the DOM. But that's a really overly-complicated way to go about doing something that...
myElement.style.width = "400px";
...can do for you in a couple of seconds. I know it doesn't really address the issue of decoupling css from js, but there's not really a whole lot you can do about that. You're trying to set css dynamically, after all.
Depending on what you're trying to accomplish, you might want to try defining multiple classes and just changing the className property in your js.
Setting the style, might be accomplished defining the inner-page style declaration.
Here is what i mean
var style = document.createElement('style');
style.type = 'text/css';
style.cssText = '.cssClass { color: #F00; }';
document.getElementsByTagName('head')[0].appendChild(style);
document.getElementById('someElementId').className = 'cssClass';
However the part of modifying it can be a lot of tricky than you think. Some regex solutions might do a good job. But here is another way, I found.
if (!document.styleSheets) return;
var csses = new Array();
if (document.styleSheets[0].cssRules) // Standards Compliant {
csses = document.styleSheets[0].cssRules;
}
else {
csses = document.styleSheets[0].rules; // IE
}
for (i=0;i<csses.length;i++) {
if ((csses[i].selectorText.toLowerCase()=='.cssClass') || (thecss[i].selectorText.toLowerCase()=='.borders'))
{
thecss[i].style.cssText="color:#000";
}
}
could you use jQuery on this? You could use
$(".class").css("property", val); /* or use the .width property */
There is a jQuery plugin called jQuery Rule,
http://flesler.blogspot.com/2007/11/jqueryrule.html
I tried it to dynamically set some div sizes of a board game. It works in FireFox, not in Chrome. I didn't try IE9.

changing css class model using JavaScript

Is it in any way possible, to change a css class model using JavaScript?
Pseudo code:
function updateClass(className, newData) {
cssInterface.alterClass(className, newData);
}
className being the name of the class, which is supposed to be changed (like ".footer") and newData being the new class content (like border: "1px solid pink;").
The target is, actually, just to save space: I am working with CSS3-animations, so changing one attribute of an element, which is affected by it's class, will terminate the animation of of it - The (in my case) font size won't change anymore. Using different classes will require an entire new set of classes for all affected elements, I'd like to avoid this.
I am not searching for a change via
element.className = "foo";
or
element.style.fontSize = "15pt";
Thanks for your help, guys :)
Here's my function to do this...
function changeCSS(typeAndClass, newRule, newValue) // modify the site CSS (if requred during scaling for smaller screen sizes)
{
var thisCSS=document.styleSheets[0] // get the CSS for the site as an array
var ruleSearch=thisCSS.cssRules? thisCSS.cssRules: thisCSS.rules // work out if the browser uses cssRules or not
for (i=0; i<ruleSearch.length; i++) // for every element in the CSS array
{
if(ruleSearch[i].selectorText==typeAndClass) // find the element that matches the type and class we are looking for
{
var target=ruleSearch[i] // create a variable to hold the specific CSS element
var typeExists = 1;
break; // and stop the loop
}
}
if (typeExists)
{
target.style[newRule] = newValue; // modify the desired class (typeAndClass) element (newRule) with its new value (newValue).
}
else
{
alert(typeAndClass + " does not exist.");
}
}
Called with (example)
changeCSS("div.headerfixed","-moz-transform-origin", "100% 0%");
hope this helps.
See my answer here. To answer your question: Yes, it's possible.
#CSS3: I tried exactly the same in one of my html5 experiments. I created an extra <style> element, and changed its contentText to the CSS class definitions I needed. Of course changing the cssRule object would be much cleaner :-)
As far as I can tell the CSS object model cannot easily tell you whether you already have an existing style rule for a particular class, but you can easily append a new rule for that class and it will override any previous declaration of that style.
I found an example of creating dynamic stylesheets.
You should take a look at dojo, it has some nice features where you can do just that..
require(["dojo/dom-class"], function(domClass){
// Add a class to some node:
domClass.add("someNode", "anewClass");
});
http://dojotoolkit.org/reference-guide/1.7/dojo/addClass.html

How to check if a css rule exists

I need to check if a CSS rule exists because I want to issue some warnings if a CSS file is not included.
What is the best way of doing this?
I could filter through window.document.styleSheets.cssRules, but I'm not sure how cross-browser this is (plus I notice on Stack Overflow that object is null for styleSheet[0]).
I would also like to keep dependencies to a minimum.
Is there a straightforward way to do this? Do I just have to create matching elements and test the effects?
Edit: If not, what are the cross-browser concerns of checking window.document.styleSheets?
I don't know if this is an option for you, but if it's a single file you want to check, then you can write your error message and toggle the style to hide it in that file.
<span class="include_error">Error: CSS was not included!</span>
CSS file:
.include_error {
display: none;
visibility: hidden;
}
I test for proper CSS installation using javascript.
I have a CSS rule in my stylesheet that sets a particular id to position: absolute.
#testObject {position: absolute;}
I then programmatically create a temporary div with visibility: hidden with that ID and get the computed style position. If it's not absolute, then the desired CSS is not installed.
If you can't put your own rule in the style sheet, then you can identify one or more rules that you think are representative of the stylesheet and not likely to change and design a temporary object that should get those rules and test for their existence that way.
Or, lastly, you could try to enumerate all the external style sheets and look for a particular filename that is included.
The point here is that if you want to see if an external style sheet is included, you have to pick something about that style sheet that you can look for (filename or some rule in it or some effect it causes).
Here is what I got that works. It's similar to the answers by #Smamatti and #jfriend00 but more fleshed out. I really wish there was a way to test for rules directly but oh well.
CSS:
.my-css-loaded-marker {
z-index: -98256; /*just a random number*/
}
JS:
$(function () { //Must run on jq ready or $('body') might not exist
var dummyElement = $('<p>')
.hide().css({height: 0, width: 0})
.addClass("my-css-loaded-marker")
.appendTo("body"); //Works without this on firefox for some reason
if (dummyElement.css("z-index") != -98256 && console && console.error) {
console.error("Could not find my-app.css styles. Application requires my-app.css to be loaded in order to function properly");
}
dummyElement.remove();
});
I would use a css selector like this from within your jquery widget.
$('link[href$="my-app.css"]')
If you get a result back it means there is a link element that has a href ending with "my-app.css"
Next use this function to validate a specific css property on an element you are depending on. I would suggest something specific to you styles like the width of a container rather something random like -9999 zindex
var getStyle = function(el, styleProp) {
var x = !!el.nodeType ? el : document.getElementById(el);
if (x.currentStyle)
var y = x.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
return y;
}
Like this
getStyle($('#stats-container')[0], "width")
or
getStyle("stats-container", "width")
If you are worried about not being able to edit other people's stylesheets, you can proxy them through a stylesheet of your own, using import
#import url('http://his-stylesheet.css');
.hideErrorMessage{ ... }
This is enough if you just want to know if your code is trying to load the stylesheet but won't help if you need to know if the foreign stylesheet was then loaded correctly.

Categories

Resources