Javascript, CSS: Get element by style attribute - javascript

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>

Related

Can I programmatically traverse a CSS stylesheet?

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.

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.

jquery if (css_attribute = value)

Is it possible to do an if statement in javascript/jquery to determine:
If the value of a certain element's css attribute is equal to a given value?
such as:
if( $('.box').css(background-color = blue) ){
// do this...
}
The css JQuery method, when given only one parameter, will return the value for the given paramenter. You can try this:
var color = $('.box').css('background-color');
if (color == 'rgb(0, 0, 255)' || color == 'blue') // =='blue' <- IE hack
alert("it's blue!\nColor detected: " + color);
The above sample will only work for the first element in the JQuery selector, therefore you should use the .each JQuery method to iterate through the whole selector if you have more than one element in it.
Please note that the .css JQuery method returns a RGB combination in most browsers (except IE), so testing it against a string such as blue will not suffice for non-IE browsers. You can test that in the JQuery API site and my fiddle.
And here's with the proper .each iteration:
$('.box').each(function(i){
var color = $(this).css('background-color');
if (color == 'rgb(0, 0, 255)' || color == 'blue') // =='blue' <- IE hack
alert("div " + i + " is blue!\nColor detected: " + color);
});​
JSFiddle
Edit: over a year and half later, I feel like this answer deserves an update.
For most use cases, I'd recommend using a CSS class:
.blue {
color: blue;
}
This allows the usage of the addClass(), removeClass(), toggleClass() methods for manipulation, as well as hasClass() for checking:
if ( $('#myElem').hasClass('blue') ) {
//it has the .blue class!
}
This works seamlessly in all browsers without needing hacks, and as a plus you get better separation of concerns -- that is, if your styling ever changes, say .blue { color: lightblue; }, the JS will keep working without modifications.
Note: of course, this approach is not suitable for a couple rare use cases where the color value is dynamically generated (e.g. using Math.random() or picking a color value off a canvas pixel), in these rare use cases you can still use the first solution in this answer.
You should be able to do something like this:
if ($('.box').css('background-color') === 'blue')
{// do this...}
check for spaces in
if($(this).css('color') == 'rgb(251, 176, 64)')
"SPACE AFTER COMA"
boxes = $('.box');
for(var i = 0;i<boxes.length;i++) {
if(boxes[i].style.backgroundColor =="blue") {
//do stuff
}
}
None of the above seems to work here in 2015. Testing a link's color against the :visited color seems to be true in all cases in Chrome for me. Also if you are not 'sniffing' and genuinely want to know if a link has been clicked during their visit to YOUR website I've found that a simple:
$("a").addClass('visited');
and checking for the class works just fine.
if ( $("a").hasClass('visited'); ){ console.log('do something honorable'); }

Iterate over dom elements using document.getElementsByTagName, pass element as jquery object

What I need is to iterate over the dom at some start element and then go through all elements below the start element.
Here is what I was doing so far.
function iterDomFromStartElem = function(startElem, callBackFunc) {
if (startElem !== null) {
var items = startElem.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
callBackFunc(items[i]);
}
}
}
The reason why I need to iterate over the dom from some start element is because our team recently got a request to implement font resizing; however, we developed are site statically with font-size in many different places using pixels. I realize that the easier approach would be to refactor the existing code, set a static font size at the root of the page, and use em's/percentages else where, so that if the business owner wanted to have a resize control on the pages, all we would have to do is increase the font-size in one spot. This refactor would require many hours, and i have been tasked with this using the least amount of man hours.
So, then, I have a call back defined like so,
function resizeFont(startElem, scale) {
iterDomFromStartElem(startElem, function(node) {
// get current size of node, apply scale, increase font size
}
}
Using this raw javascript would work but i'm having trouble getting font-size if its declared inside a css class.
I know that jquery has a css property and if I had a jquery object I could do $(this).css(....), so,
when I call callBackFunc(items[i]), how can I convert the items[i] into a jquery object so that in my call back function, I can do node.css(......)?
I guess I could do $(items[i].id), perhaps that would be the simplest.
Is there an easier way with javascript to determine the font size even if that font size is declared in a css class and that css class is attached to the element?
Preface: I think you're better off fixing the problem properly. You might save an hour or two now by taking a shortcut, but it's likely to cost you in the long term.
But re your actual question:
how can I convert the items[i] into a jquery object so that in my call back function, I can do node.css(......)?
If you pass a raw DOM object into $(), jQuery will return a wrapper around it. You don't have to go via the ID.
You can also get a jQuery instance for all descendant elements of a given starting point, like this:
var x = $("#starting_point *");
...although you'd still end up creating a lot of temporary objects if you then looped through it, like this:
$("#starting_point *").each(function() {
// Here, `this` is the raw DOM element
});
Here's an example of looping all elements under a given starting point with jQuery, in this case showing their tag and id (if any) and turning them blue (live copy):
$("#start *").each(function() {
display(this.tagName + "#" + (this.id || "?"));
$(this).css("color", "blue");
});
Note I said under. If you also want to include #start, the selector changes to #start, #start *.
Here's a complete example of increasing the font size of elements starting with (and including) a given start point, where the font size is variously set by inline and stylesheet styles (live copy):
CSS:
.x13 {
font-size: 13px;
}
.x17 {
font-size: 17px;
}
.x20 {
font-size: 20px;
}
HTML:
<input type="button" id="btnBigger" value="Bigger">
<div id="start" class="x13">
This is in 13px
<p style="font-size: 15px">This is in 15px
<span class="x17">and this is 17px</span></p>
<ul>
<li id="the_list_item" style="10px">10px
<strong style="font-size: 8px">8px
<em class="x20">five</em>
</strong>
</li>
</ul>
</div>
JavaScript:
jQuery(function($) {
$("#btnBigger").click(function() {
$("#start, #start *").each(function() {
var $this = $(this),
fontSize = parseInt($this.css("font-size"), 10);
display("fontSize = " + fontSize);
$this.css("font-size", (fontSize + 2) + "px");
});
});
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
});

Need Javascript syntax to reference a CSS class instead of HTML element

I searched online for the correct syntax to reference a CSS class, instead of an HTML element, but was unable to find anything helpful.
I would like to modify the code below to reference any DIV of class buy_content "div.buy_content" instead of the body element.
Small Text
Medium Text
Large Text
There is no "JavaScript syntax" for what you're asking for. Newer browsers support an API called "getElementsByClassName", so you could do this:
function setSize(sz) {
var elements = document.getElementsByClassName('buy_content');
for (var i = 0; i < elements.length; ++i) {
if (elements[i].tagName === 'DIV')
elements[i].style.fontSize = sz;
}
}
<a href='#' onclick='setSize("1em"); set_cookie(...);'>Small</a>
You can find a "patch" for "getElementsByClassName" support here.
<a href="#" class="clickie size-1" >Small text </a>
<a href="#" class="clickie size-2" >Medium text </a>
<a href="#" class="clickie size-3" >Large text </a>
You should change the markup not to rely on inline javascript.
// bind the event handler to all <a> tags
var as = document.getElementsByTagNames("a");
for (var i = 0, ii = as.length; i < ii; i++) {
as[i].onclick = setText;
}
function setText(ev) {
// get the em size from the class
var size = /[.]*text-([\d][.]*)/.exec(ev.target.className)[1]
var divs = document.querySelectorAll("div.buy_content");
// set the style on all divs.
for (var i = 0, ii = divs.length; i < ii; i++) {
divs[i].style.fontSize = size + "em";
}
}
There are issues with browser support (mainly IE7 and lower) so you need some more boilerplate to make it work.
You can't really do this (easily/readably/cleanly) with inline and stock JavaScript because the JavaScript DOM API doesn't provide a way to reference a CSS class since this isn't part of the DOM. You would have to populate an array or list with HTML elements that have that class applied to them and then iterate over the collection.
JQuery provides selectors and iterators to make this very simple, but if you can't use libraries then doing this inline isn't a good idea. Put it in a function in a script block or an external .js file.
EDIT:
A few people pointed out querySelectorAll, which will select by class but from what I have read isn't completely cross platform (doesn't work on IE below IE 8).
Further, to clarify on my original post, when I said that the DOM API doesn't allow you to access an element by class, what I meant was that it couldn't be done with DOM traversal. querySelectAll or the JQuery selectors perform DOM traversal with functions that inspect elements and their properties, retrieve the objects, and populate collections. Even getElementById performs attribute inspection. I suppose, in retrospect, it's a moot point, but since he wasn't using selectors or attribute queries in his original code I thought that he was asking if there was JS syntax that was as simple as what he was currently using. That's why I mentioned functions. In my head, even something like getElementById is a function since, well, it is a function.
I believe what you are looking for is insertRule (this is exactly what you asked for... kinda):
document.styleSheets[document.styleSheets.length-1].insertRule('div.buy_content {font-size: 1em}',document.styleSheets[document.styleSheets.length-1].length)
document.styleSheets[document.styleSheets.length-1] is your last stylesheet.
the new rule will go at index document.styleSheets[document.styleSheets.length].length
http://www.quirksmode.org/dom/w3c_css.html#t22
also... deleteRule:
http://www.quirksmode.org/dom/w3c_css.html#t21
BUT, a better way to go would be to getElementsByClassName, loop through em, check their nodeName for "DIV", then apply the styles the old fashioned way.
Leverage CSS to do the selection work for you:
body.smalltext .buy_content { font-size: 1em; }
body.mediumtext .buy_content { font-size: 2em; }
body.largetext .buy_content { font-size: 3em; }
...
<input type="button" value="Small text" id="smalltext"/>
<input type="button" value="Medium text" id="mediumtext"/>
<input type="button" value="Large text" id="largetext"/>
...
document.getElementById('smalltext').onclick= function() {
document.body.className= 'smalltext';
};
document.getElementById('mediumtext').onclick= function() {
document.body.className= 'mediumtext';
};
document.getElementById('largetext').onclick= function() {
document.body.className= 'largetext';
};
My first suggestion to answer your exact question:
If your project is bigger in scope than just this one thing:
Download jQuery
Use code:
$('div.buy_content')
Which returns a jQuery array object of all the divs which you can further manipulate.
My second suggestion based on thinking more deeply about what you're trying to do:
Either completely replace the stylesheet in script or modify the existing stylesheet to change the style. Don't loop through all the DIVs in the document and change their style assignment, instead change the meaning of their already-assigned style.

Categories

Resources