PDA

View Full Version : change background color on tag with multiple classes



marilynn.fowler
Mar 9th, 2007, 05:08 AM
I have a document where each "A" tag has multiple classes. I have a list of categories. I want to be able to click on a category and have the "A" tag background color change. For example, if I click on the "retail" category, I want any "A" tag with the retail class to turn grey. Since there's no getElementByClass, how do I do this? Here's my attempt, which didn't work:



<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><head>
<title>colored lists</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>

<style type="text/css">
a { text-decoration: none; font-weight: bold; }
a:link, a:visited { color: #903; }
a:hover { color: #000; }
li { list-style-type: none; background-color: #eef; width: 137px; }
li a { display: block; padding: 5px 10px; border: 1px solid #fff; width: 135px; }
</style>
<script type="text/javascript">
function lightMe(whichClass) {
if (document.getElementById) {
document.getElementByTagName('a').className(whichClass).style.backgroundColor="#333";
}
}
</script>
<body>
<div id="nav">
<ul>
<li><a href="#" class="uno tres seis">subsection 1</a></li>
<li><a href="#" class="uno quatro siete">subsection 2</a></li>
<li><a href="#" class="uno cinco seis">subsection 3</a></li>
<li><a href="#" class="dos tres siete">subsection 4</a></li>
<li><a href="#" class="dos quatro seis">subsection 5</a></li>
<li><a href="#" class="dos cinco siete">subsection 6</a></li>
<li><a href="#" class="dos tres seis">subsection 7</a></li>
<li><a href="#" class="dos quatro ocho">subsection 8</a></li>
<li><a href="#" class="dos cinco ocho">subsection 9</a></li>
</ul>
</div>
<p><a href="#" onclick="return lightMe('uno');">uno</a><br />
<a href="#" onclick="return lightMe('dos');">dos</a><br />
<a href="#" onclick="return lightMe('tres');">tres</a><br />
<a href="#" onclick="return lightMe('quatro');">quatro</a><br />
<a href="#" onclick="return lightMe('cinco');">cinco</a><br />
<a href="#" onclick="return lightMe('seis');">seis</a><br />
<a href="#" onclick="return lightMe('siete');">siete</a><br />
<a href="#" onclick="return lightMe('ocho');">ocho</a></p>
</body>

</html>

phoenixshade
Mar 9th, 2007, 06:13 AM
I've constructed a function that will change the color of all elements with a certain class. Here's the function:

function bgcolorElementsByClassName(classname,color) {
var regex=new RegExp("\\b"+classname+"\\b")
var alltags=document.getElementsByTagName("*")
for (i=0; i<alltags.length; i++) {
name=alltags[i].className
if (name.match(regex))
alltags[i].style.backgroundColor=color
}
}
A function call of colorElementsByClassName('test','#FFCCCC') was tested on these elements:

<span class="test">Red background</span>
<span class="another test">Red background</span>
<span class="test three">Red background</span>
<span class="one test three">Red background</span>
<span class="nottest">Normal background</span>
<span>Normal background</span>
It produced the expected output, properly selecting elements with multiple classes, and properly not selecting elements where the classname in the function matched a substring of the span's class (the <span class="nottest"> above).

I'd suggest that when you implement this, you keep a variable with the currently highlighted class, so that you can easily return these to normal when a new class is to be highlighted.

phoenixshade
Mar 9th, 2007, 08:44 AM
Implemented into your example:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
a { text-decoration: none; font-weight: bold; }
a:link, a:visited { color: #903; }
a:hover { color: #000; }
li { list-style-type: none; background-color: #eef; width: 137px; }
li a { display: block; padding: 5px 10px; border: 1px solid #fff; width: 115px; }
</style>
<script type="text/javascript">
var last='none';

function bgcolorClass(classname,color) {
var regex=new RegExp("\\b"+classname+"\\b")
var alltags=document.getElementsByTagName("*")
for (i=0; i<alltags.length; i++) {
name=alltags[i].className
if (name.match(regex))
alltags[i].style.backgroundColor=color
}
}

function highLight(oldClass,oldColor,newClass,newColor) {
bgcolorClass(oldClass,oldColor)
bgcolorClass(newClass,newColor)
return newClass
}
</script>
</head>
<body>
<div id="nav">
<ul>
<li><a href="#" class="uno tres seis">subsection 1</a></li>
<li><a href="#" class="uno quatro siete">subsection 2</a></li>
<li><a href="#" class="uno cinco seis">subsection 3</a></li>
<li><a href="#" class="dos tres siete">subsection 4</a></li>
<li><a href="#" class="dos quatro seis">subsection 5</a></li>
<li><a href="#" class="dos cinco siete">subsection 6</a></li>
<li><a href="#" class="dos tres seis">subsection 7</a></li>
<li><a href="#" class="dos quatro ocho">subsection 8</a></li>
<li><a href="#" class="dos cinco ocho">subsection 9</a></li>
</ul>
</div>
<p><a href="#" onclick="last=highLight(last,'#EEEEFF','uno','#CCCCFF');">uno</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','dos','#CCCCFF');">dos</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','tres','#CCCCFF');">tres</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','quatro','#CCCCFF');">quatro</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','cinco','#CCCCFF');">cinco</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','seis','#CCCCFF');">seis</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','siete','#CCCCFF');">siete</a><br />
<a href="#" onclick="last=highLight(last,'#EEEEFF','ocho','#CCCCFF');">ocho</a></p>
</body>
</html>
Tested in IE7 and FF2.

marilynn.fowler
Mar 11th, 2007, 10:01 PM
This looks tasty! I'm going to try it. Thanks much.
I love brainy people.

marilynn.fowler
Mar 11th, 2007, 10:54 PM
Is there any way I can specify the color in the javascript script instead of the HTML onclick? That way if I had to change the color scheme I'd only have to make my edits in one external script. I'm thinking along the lines of
onclick="last=highlight(last,'uno')"

I'll admit this script went over my head (in a good way) and I'm trying to decipher it line by line. (I like to understand scripts so that I can properly modify them.)

What is the purpose of \\b in the code?

phoenixshade
Mar 12th, 2007, 03:40 AM
I've improved the code even more than you asked, so that you simply need to use onclick="highLight('uno')" for example. This works because last is declared outside of all functions, making it a global variable, so it can be set in the function and keeps its value everywhere. (Contrast that with the local variable regex, which is declared inside a function. When the function exits, the variable disappears, too.)

Here's the improved script. (I've also commented it so that you can see what it's doing.):

var last='none' \\ the currently highlighted class
var active='#CCCCFF' \\ the highlight color
var normal='#EEEEFF' \\ the normal color

// Change background color of all elements with classname to color
function bgcolorClass(classname,color) {
var regex=new RegExp("\\b"+classname+"\\b") // regex will match classname if both ends are word boundaries
var alltags=document.getElementsByTagName("*") // put all elements in alltags[]
for (var i=0; i<alltags.length; i++) { // loop through alltags[]
var name=alltags[i].className // put class in name
if (name.match(regex)) // if regex is in name...
alltags[i].style.backgroundColor=color // ... set background color
}
}

// Out with the old, in with the new
function highLight(highlightClass) {
bgcolorClass(last,normal) // Change color of last to normal
bgcolorClass(highlightClass,active) // Change color of all highlightClass elements to active
last=highlightClass // Keep track of highlight state
}
Now, you only need to change the values of active and normal at the head of the script.

As for your question about the \\b, that's a regex thing. \b means a word boundary— either a space, tab, linefeed, carriage return, or teminal of string matches it. That's how I got the script to match classes at the start or end of the string, but to not match a substring of one of the classes. Since the regex is built from a string in the RegExp() function, I needed to escape the backslash, hence the "\\b". (Hope that made sense...)

marilynn.fowler
Mar 12th, 2007, 05:59 PM
Thank you so much, phoenixshade, especially for COMMENTING the code! Now I can follow what's going on, especially in the concepts that are unfamiliar to me. I might even learn something today. : )

And you'll get the credit for the code in my external script. Do you want to be phoenixshade or Psychotic Crack-Smoking Monkeys?

phoenixshade
Mar 12th, 2007, 06:38 PM
Thank you so much, phoenixshade, especially for COMMENTING the code! Now I can follow what's going on, especially in the concepts that are unfamiliar to me. I might even learn something today. : )

And you'll get the credit for the code in my external script. Do you want to be phoenixshade or Psychotic Crack-Smoking Monkeys?

LOL... my real name is fine. I'm trying to SEO myself for programming and design. If I'm ever in SF, you owe me a lunch.

Arbitrator
Mar 12th, 2007, 07:28 PM
It would probably be better to keep the CSS in a style sheet instead of in a script; this is more maintainable.

Style in Script (Current):

alltags[i].style.backgroundColor=color

Style in Style Sheet:


.special { background-color: white; }

alltags[i].setAttribute("class", "special");

phoenixshade
Mar 12th, 2007, 07:57 PM
It would probably be better to keep the CSS in a style sheet instead of in a script; this is more maintainable.


.special { background-color: white; }

alltags[i].setAttribute("class", "special");


In my opinion, this is behavior, not presentation. Yes, it could go into the css, but there's an additional cost.

Using setAttribute in that way will destroy the original classes of the object. These must be retained in order for the script (or any script that does what she asked) to function properly. With your code, if someone clicks "uno" it will work; if they then click "dos" it works again, but if they now click "uno" again, there are no longer any elements with that class name, so nothing is highlighted.

Doing this also makes the function less flexible and therefore less reusable. I'd need two separate functions where I now have one (or else some logical switching) to do whichever of these is appropriate:

alltags[i].className+=' highlight';
...
alltags[i].className.replace(' highlight','');
This solution doesn't appeal to me. The current version is concise and easily understood, plus it keeps behavior separate from presentation.

Besides, how hard is it ultimately to change the values of two global variables declared at the head of the script? Nothing I'd consider a maintenance issue.

Just my opinion. To each his (or her) own.

Arbitrator
Mar 12th, 2007, 09:03 PM
In my opinion, this is behavior, not presentation. …The current version is concise and easily understood, plus it keeps behavior separate from presentation.I disagree that setting a CSS property is not considered presentation. style.backgroundColor is functionally equivalent to setting the CSS’s background-color property.


Yes, it could go into the css, but there's an additional cost.Of course. Naturally, you weigh costs and some will disagree on which cost is higher.


Using setAttribute in that way will destroy the original classes of the object.I am aware of this; it was a generalized example. Using the verbose, but clear, W3C DOM:


if (alltags[i].getAttribute("class")) {
alltags[i].setAttribute("class", alltags[i].getAttribute("class") + " special");
}
else {
alltags[i].setAttribute("class", "special");
}

Disclaimer: Still a general example; not a full script.


Doing this makes the function less flexible and therefore less reusable.I’m not sure where this comes in. I haven’t examined your script in detail though.

I can say that your names are pretty specific to be targeted at re-usability though; what if I want to change the font instead? I don’t see where it would be less flexible.


Besides, how hard is it ultimately to change the values of two global variables declared at the head of the script? Nothing I'd consider a maintenance issue.Here are some maintenance issues:

New author that doesn’t know JavaScript wants to change the presentation.
Styles conflict, the presentation has problems, or the CSS otherwise needs be updated; the author must look for and alter presentation at multiple locations.
Consistency. This method may be less code for one declaration, but for many declarations, it would likely be more code. One or many, I would go for consistency and use the same general rule: assignment to a class.

phoenixshade
Mar 14th, 2007, 12:14 AM
I'm still not totally convinced, but I'm rewriting the script to allow the background color to be determined by the style sheet anyway. However, I've run across a problem.

alltags[i].setAttribute("class", alltags[i].getAttribute("class") + " special");
This doesn't work in IE7. I can set the attribute, and I can confirm with a getAttribute("class") that the class has been added, but for some reason the styling for the class is not applied. However, if I do it this way:

alltags[i].className += " special";
it works. Anyone else ever encounter this?

Both methods work in FF2.

Arbitrator
Mar 15th, 2007, 07:30 AM
This doesn't work in IE7. I can set the attribute, and I can confirm with a getAttribute("class") that the class has been added, but for some reason the styling for the class is not applied.*sigh* Internet Explorer never ceases to annoy me. This example should make things clear as to what’s going on:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html lang="en-US">
<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">

<title>HTML 4.01 Strict Document</title>

<meta name="Author" content="Patrick Garies">

<style type="text/css">
* { margin: 0; }
html { padding: 1em; }
p { margin: 0 0 1em; }
div { width: 500px; height: 500px; background: red; }
div.original { border: 10px outset lime; }
div.special { background: green; }
</style>

<script type="text/javascript">
function concatenate() {
var div = document.getElementsByTagName("div")[0];
if (div.getAttribute("className")) {
div.setAttribute("className", div.getAttribute("className") + " special");
}
else {
div.setAttribute("class", div.getAttribute("class") + " special");
}
}
</script>

</head>
<body onload="concatenate();">

<p>The box below should be filled with green and have an outset border ten pixels in width and lime in color.</p>

<div class="original"></div>

</body>
</html>

phoenixshade
Mar 15th, 2007, 08:17 AM
Thanks for the if (object.getAttribute("className")) { } else { } -- that's the bit I needed.

marilynn.fowler
Mar 29th, 2007, 07:18 AM
Thanks, you guys, for going to all this effort. Maybe one day I'll be able to do the same for someone else.