Color Alternate Rows

Ron, together with something that I realized today, that means that after an Ajax altering of the HTML, even a recall of the getElementsByClassName-based function won’t repaint the child elements correctly. The thing is that the classes that were Javascript-set previously are not removed, and new classes are only added. Which means that if the number of child elements changes from odd to even or vice-versa, all of them will be colored pink, in my example.

There are several work-arounds for that, of which I will give you the simplest to begin with. This method assumes that the targeted child elements do not have any other classes set, neither hard-coded in the HTML nor by means of Javascript. This is the code (live demo), which should be self-explanatory:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Remove class name, simple method</title>
<style>
.odd {
    background-color: pink;
}
table {
    border-collapse: collapse;
    margin-bottom: 1em;
}
td {
    border: 1px solid black;
}
button {
    width: 250px;
}
</style>
</head>
<body>

    <div class="alternColorParent">
        <p>I am an odd paragraph <span>with a span</span></p>
        <p>I am an even paragraph</p>
        <p>I am an odd paragraph</p>
        <p>I am an even paragraph</p>
        <p>I am an odd paragraph</p>
        <p>I am an even paragraph</p>
        <p>I am an odd paragraph</p>
    </div>

    <ul class="alternColorParent">
        <li>I am an odd list item</li>
        <li>I am an even list item</li>
        <li>I am an odd list item</li>
        <li>I am an even list item</li>
        <li>I am an odd list item</li>
        <li>I am an even list item</li>
        <li>I am an odd list item</li>
    </ul>

    <table class="alternColorParent">
        <tbody>
            <tr>
                <td>I am the first cell in an odd table row</td>
                <td>I am the second cell in an odd table row</td>
            </tr>
            <tr>
                <td>I am the first cell in an even table row</td>
                <td>I am the second cell in an even table row</td>
            </tr>
            <tr>
                <td>I am the first cell in an odd table row</td>
                <td>I am the second cell in an odd table row</td>
            </tr>
            <tr>
                <td>I am the first cell in an even table row</td>
                <td>I am the second cell in an even table row</td>
            </tr>
            <tr>
                <td>I am the first cell in an odd table row</td>
                <td>I am the second cell in an odd table row</td>
            </tr>
            <tr>
                <td>I am the first cell in an even table row</td>
                <td>I am the second cell in an even table row</td>
            </tr>
        </tbody>
    </table>

    <button type="button" onclick="setClassName('odd')">Set class name 'odd'</button>
        <br>
    <button type="button" onclick="removeClassName('odd')">Remove class name 'odd'</button>
        <br>
    <button type="button" onclick="setClassNameAnew('odd')">Set class name 'odd' anew</button>

<script>
function setClassName(declaredClassName) {
    var alternColorParents = document.querySelectorAll('.alternColorParent');
    for (var i=0; i<alternColorParents.length; i++) {
        var alternColorParent = alternColorParents[i];
        if (alternColorParent.tagName == 'TABLE') { // must be uppercase
            var alternColorChildren = alternColorParent.querySelectorAll('tr');
        }
        else {
            var alternColorChildren = alternColorParent.querySelectorAll('.alternColorParent > *');
        }
        for (var k=0; k<alternColorChildren.length; k+=2) {
            alternColorChildren[k].className = declaredClassName;
        }
    }
}

function removeClassName(declaredClassName) {
    var allTargetedElements = document.querySelectorAll('.'+declaredClassName);
    for (var i=0; i<allTargetedElements.length; i++) {
        allTargetedElements[i].className = ''; // two single quotes, therefore: empty
    }
}

function setClassNameAnew(declaredClassName) {
    removeClassName(declaredClassName);
    setClassName(declaredClassName);
}
</script>
</body>
</html>

There are also methods for when the child elements do have other class names, with a so-called regular expression or the split method, but I am yet to get either of them working in this setting. Creating a live demo or even a code that includes an Ajax update would be too time-consuming for me, so you will just have to believe that the script also works in IE8 and other lesser browsing Gods.

I would suggest to post a new question by the time you would need such a code. If you would want that at all, since the HTML should be kept as clean as possible, as you stated yourself in your original post.

You will also notice that I made the code more generic/universal than thus far.

Functions for adding and removing classes that don’t assume it is the only class:

addClass = function(el,cl) {
var re = new RegExp('\\\\b'+cl+'\\\\b');
if (!el.className.match(re)) el.className += " "+cl;
}
 
removeClass function(el,cl) {
var re = new RegExp('\\\\b'+cl+'\\\\b');
if (el.className.match(re))
el.className=el.className.replace(re,' ');
}

For more modern browsers you can use the classList.add and classList.remove built in methods.

To add class names we already have a much simpler script: element.className += ’ odd’. And unfortunately, your removal script, apart from that it misses an =, breaks in some cases of hyphen use in the class names. A hyphen is a word boundary, too. This script solves that:


function removeClassName(declaredClassName) {
    var allTargetedElements = document.querySelectorAll('.'+declaredClassName);
    for (var i=0; i<allTargetedElements.length; i++) {
        var targetedElement = allTargetedElements[i];
        var allClasses = targetedElement.className;
        var classRegex = new RegExp('^('+declaredClassName+')$|(\\\\s'+declaredClassName+'\\\\b)');
        if (classRegex.test(allClasses) == true) {
            targetedElement.className = targetedElement.className.replace(declaredClassName,''); // two single quotes
        }
    }
}

Regex demo on http://codepen.io/anon/pen/Hhswl?editors=100, pattern demo on http://regex101.com/r/pP8nS2/1.

My code in post #43 was an improvement in terms of regular expression, but it wasn’t fool-proof. This script should be:


function removeClassName(declaredClassName) {
    var allTargetedElements = document.querySelectorAll('.'+declaredClassName);
    for (var i=0; i<allTargetedElements.length; i++) {
        var targetedElement = allTargetedElements[i];
        var allClasses = targetedElement.className;
        var classRegex = new RegExp('^('+declaredClassName+')$|([^\\\\-\\\\w\\\\d]'+declaredClassName+'[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*'+declaredClassName+'\\\\s)');
        if (classRegex.test(allClasses) == true) {
            targetedElement.className = targetedElement.className.replace(classRegex,'');
        }
    }
}

Live demo of the whole shebang on http://jsbin.com/zaziz/1/edit?html,output. Note that that starts off with a couple of even children colored pink, due to hard-coded classes, which is for testing purposes.

I’m not gonna explain the working of the regular expression, Ron, because that would take way too much time. But neither should you want to know. Not at this stage, anyway. What you do need to know about Javascript is that (only) className is a so-called reserved term, a term that is incorporated in Javascript. If you look closely at the (JS-highlighted) code, you will see that that is colored purple. All other words with ‘className’ in it are either variables, which have to be declared, or parameters/arguments.

Lastly, the folks who developed the regex syntax should get the Nobelpenalty for logical and especially intuitive syntax!

Hope you don’t mind, but noticed you are declaring variables and creating regex objects repetitively within a loop. I think it would be better to declare them at the top of your function. Got caught out doing this myself on JSlint the other day.

Also you assign targetedElement.className to allClasses then proceed to uses targetedElement.className after that. Better to use allClasses. No?

Hopefully I haven’t broken it:)

function removeClassName(declaredClassName) {
  var allTargetedElements = document.querySelectorAll('.'+declaredClassName),
      targetedElement,
      allClasses,
      classRegex = new RegExp(['^(', declaredClassName,
                               ')$|([^\\\\-\\\\w\\\\d]',
                               declaredClassName,
                               '[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*',
                               declaredClassName,'\\\\s)'].join(''));

    for (var i=0; i<allTargetedElements.length; i++) {
        targetedElement = allTargetedElements[i];
        allClasses = targetedElement.className;
        if (classRegex.test(allClasses) === true) {
          allClasses = allClasses.replace(classRegex,'');
        }
    }
}

Just a note, which is picked up on in John Resig’s Javascript Ninja book.

Rooted queryselect’s can be a bit unpredictable. Having to read up on this, but it looks like the query in certain cases only picks up on the last part of the selector.

An example might be best:

<!DOCTYPE html>
<html lang="en">
<head> <meta charset="utf-8">
<title>Root QuerySelector</title>
</head>
<body>
<div id="test">
  <b>Hello</b>
  <div>
    <b>Hello here too!</b>
  </div>
</div>
</body>
<script> *
var b = document.getElementById("test").querySelectorAll("div b");
console.log(b); // Will log 2 nodes <b>Hello</b> and <b>Hello here too!</b>
</script>
</html>

Beyond repair. :slight_smile: For two reasons:

  1. The array & join method – the plus is a function, and even if you add the plusses back, it throws a syntax error.
  2. Keep using allClasses after it has been declared. Why exactly that is I don’t understand, but I’ve seen the same in a script I wrote that used something.somestring.indexOf. When I made that into one short variable, the script broke.

That in itself doesn’t do any harm, but for a beginner like Ron, I’d think it makes the script less intuitive. And in terms of rendering speed, it would make a difference of a couple of nanoseconds at the most. I mean, can you see a delay between clicking the button “Reset class name ‘odd’ to odd childs only” and the elements coloring? And the function that is called for that has two functions in it…

Nonetheless, I do see room for improvement in terms of readability/intuitiveness:


function removeClassName(declaredClassName) {
    var classRegex = new RegExp('^('+declaredClassName+')$|([^\\\\-\\\\w\\\\d]'+declaredClassName+'[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*'+declaredClassName+'\\\\s)');
    // Don't try to understand this regex as of yet, Ron
    var allTargetedElements = document.querySelectorAll('.'+declaredClassName);
    for (var i=0; i<allTargetedElements.length; i++) {
        var targetedElem = allTargetedElements[i];
        if (classRegex.test(targetedElem.className) == true) {
            targetedElem.className = targetedElem.className.replace(classRegex,'');
        }
    }
}

The only thing Ron would have to understand for this alleged improvement is that className retrieves and sets all the class names of/on the element it is called upon. It should actually have been named className(s), if that would have been possible.

Greetings from yet another sunny and hot day in Amsterdam. Come visit us, and have a great time! :slight_smile:

I must be missing something. The plus’s are just concating the string to be processed by RegExp.

The results in my test are identical. Logged in firefox and chrome.

var declaredClassName = 'myClass';

var reg1 = new RegExp('^('+declaredClassName+')$|([^\\\\-\\\\w\\\\d]'+declaredClassName+'[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*'+declaredClassName+'\\\\s)'),
    reg2 = new RegExp(['^(', declaredClassName, 
                       ')$|([^\\\\-\\\\w\\\\d]', 
                       declaredClassName, '[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*', 
                       declaredClassName,
                       '\\\\s)'].join(''));

console.log(reg1); // RegExp /^(myClass)$|([^\\-\\w\\d]myClass[^\\-\\w\\d]*)|([^\\-\\w\\d]*myClass\\s)/
console.log(reg2); // RegExp /^(myClass)$|([^\\-\\w\\d]myClass[^\\-\\w\\d]*)|([^\\-\\w\\d]*myClass\\s)/

The advantage I see to using Array join is that you can break the regex down over multiple lines. Therefore better for commenting and being able to isolate blocks of regex code. Just more readable.

You can use + over multiple lines, but it seems that is bad practice. Is certainly pulled up in JSlint.

var allClasses = targetedElement.className; //<-----
        var classRegex = new RegExp('^('+declaredClassName+')$|([^\\\\-\\\\w\\\\d]'+declaredClassName+'[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*'+declaredClassName+'\\\\s)');
        if (classRegex.test(allClasses) == true) {
            targetedElement.className = targetedElement.className.replace(classRegex,'');
        }

You have already assigned allClasses as a reference to targetedElement.className. Yes nanoseconds, but it’s more succinct and efficient to use that in your if condition rather than targetedElement.className.

Cheers

RLM

But that allows class=“odd odd odd odd odd” whereas the one I showed makes sure the class isn’t already there before adding it.

JavaScript never plays nice with classes containing hyphens so you should never use them in your class names - besides which they were not valid before HTML 5 and for HTML 5 JavaScript has built in commands for adding and removing classes…

There is no = missing in the removeClass function I listed.

If you want to work with HTML 5 then you should use targetedElement.classList.add(‘odd’) and targetedElement.classList.remove(‘odd’) as I mentioned in the earlier post to add and remove classes using the single built in command that is there for that purpose.

It’s rather confusing why you would call that a ‘rooted querySelector(All)’, because document.querySelector(All) works perfectly, as the demos demonstrate. But indeed, combining getElementById and querySelector(All) into one concatenated subfunction will not work. The exact technical reason I couldn’t tell you, but it is a known fact that getElementsBy~ returns a dynamic node list, while querySelectorAll returns a static one. I guess the principle on which that is based upon also prohibits combining them.

The reason why I’m still using it is that Ron wanted to code for IE8 as well, which was even the primary reason for his OP question. And IE8 doesn’t support getElementsByClassName. If you would ever have to use such a concatenated subfunction and want to code for IE8 as well, you would first have to teach IE8 what getElementsByClassName entails. The following script does that, and with the new regex it should work flawlessly:


if (!document.getElementsByClassName) {
    document.getElementsByClassName = function(declaredClassName) {
        var elemArray = [];
        var elems = this.getElementsByTagName('*');
        for (var i=0; i<elems.length; i++) {
            var allClasses = elems[i].className;
            var classRegex = new RegExp('^('+declaredClassName+')$|([^\\\\-\\\\w\\\\d]'+declaredClassName+'[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*'+declaredClassName+'\\\\s)');
            if (classRegex.test(allClasses) == true)
                elemArray.push(elems[i]);
        }
        return elemArray;
    }
}

The exact technical reason I couldn’t tell you, but it is a known fact that getElementsBy~ returns a dynamic node list, while querySelectorAll returns a static one. I guess the principle on which that is based upon also prohibits combining them.

It’s not that. I would have to dig into it again. If I’m right it comes down to the way querySelector works. As I remember t’s a top down selector. It traverses down through the nodes and for reasons I forget (It’s late here) it makes this error. A bottom up selector would not make the same error.

John Resig uses a fix for this where he assigns a temporary id to the node to enforce the element root. Sketchy right now, but will have a look over the script again to get a better understanding.

That may be true, but just try it out for yourself, as I did – with your script, the demo I linked to becomes dysfunctional.

The nanoseconds thing was in response to your suggestion of declaring the variables in one go, at the top of the function. Using [FONT=book antiqua]allClasses[FONT=arial] also in setting the new class(es) simply breaks the script. Again, just try it out.

Boy, you must have a really, really bad off-day, Felgall. First of all, HTML5 has little to do with Javascript feature support. IE6 supports the HTML5 doctype, even though it doesn’t support its new elements, but lacks much of the modern-day JS-feature support. There is no such thing as “HTML5 Javascript”. Those are two different things. [/FONT]
[/FONT]
Secondly, it is plain BS that JS does not play well with classes containing hyphens. Just quote them, and everything goes well, apart from regexes. 90% of the websites have classes with hyphens, and the JSs called upon them function perfectly well! That does not mean that it would not be very good practice to either camelCase or under_score class names, but what you’re saying is ludicrous!

Thirdly, not even IE9 supports classList, while the OP specifically asked for IE8 support. And we must assume that there are many people who have their Firefox set to ‘Do not update’, e.g. because add-ons have shown to be incompatible with higher browser versions.

Fourthly, the script you gave is missing an =:


removeClass  function(el,cl) {  // There should be an = between 'removeClass' and 'function'. 
    var re = new RegExp('\\\\b'+cl+'\\\\b'); 
    if (el.className.match(re)) 
        el.className=el.className.replace(re,' '); 
}

And besides that, the regex simply is a grossly dysfunctional one. With it, a removal of the class ‘user’ in a (non-intentionally targeted) class=“user-name” would result in class=“-name” with your script.

The argument of your addClass script first checking whether the class is already set is a valid one in itself, but that’s all I’ll give you.

Time to call it a day, Felgall, even though it ain’t 5 pm yet, Down Under?

HTML 5 doesn’t have a doctype of its own. The tag it is using is the short version of the HTML 2 doctype which has been around since long before IE6 (which is why IE6 accepts it). The JavaScript API that adds the classList object is part of a group of W3C standards associated with HTML 5 (since it is intended to work with the HTML 5 DOM and not the current HTML 4 DOM).

You have just contradicted yourself and agreed with me that they have issues. Anyway why use hyphens when camelCase saves you a character and ALWAYS works.

As I said - it is ludicrous to use - in class names when you expect to use them with JavaScript because they don’t always work AND you can save yourself a character in each reference by leaving out the completely unnecessary but proiblematic hyphen.

I have no i9dea whyt anyone would want to use class---------------------------------name (if one hyphen is reasonable then lots of hyphens must be even better) as a class name in their page when className is just as easy to read and doesn’t have a lot of the same issues.

Anyway, here’s a corrected version of the two functions that will work if you insist on using hyphens or other characters that are supposed to indicate a word break in JavaScript.

'[addClass = function(el,cl) {
var re = new RegExp('[^|\\\\s]'+cl+['\\\\s|$]');
if (!el.className.match(re)) el.className += " "+cl;
}
 
removeClass = function(el,cl) {
var re = new RegExp('[^|\\\\s]'+cl+['\\\\s|$]');
if (el.className.match(re))
el.className=el.className.replace(re,' ');
}

Let’s first establish that the = was indeed missing. It would have been nice if you would have admitted that.

Further, even without calling the removal script, it throws an error alert at page load. You have the third single-quote character position wrong. It should be before the square bracket. And even after correcting that, it proves to be a completely dysfunctional regex. See http://jsbin.com/gayok/1/edit?html,output.

For the rest of your post, I see no reasonable and valid arguments either.

Stupid mistake on my part. Should have tested it, but was busy on another project.

allClasses = targetedElement.className;

Obvious now targetedElement.className returns a value. In this case a string. Not a reference to the property. So all allClasses ends up being a string.

typeof allClasses -> string

A bit like you couldn’t set myBgColour if myBgColour = elem.style.backgroundColor.

So this will work. Somewhere in-between

targetedElement.className = allClasses.replace(classRegex,'');
function removeClassName(declaredClassName) {
  var allTargetedElements = document.querySelectorAll('.'+declaredClassName),
      targetedElement,
      allClasses,
      classRegex = new RegExp(['^(', declaredClassName,
                               ')$|([^\\\\-\\\\w\\\\d]',
                               declaredClassName,
                               '[^\\\\-\\\\w\\\\d]*)|([^\\\\-\\\\w\\\\d]*',
                               declaredClassName,'\\\\s)'].join(''));
 
    for (var i=0; i<allTargetedElements.length; i++) {
        targetedElement = allTargetedElements[i];
        allClasses = targetedElement.className;
        if (classRegex.test(allClasses) === true) {
          targetedElement.className = allClasses.replace(classRegex,'');
        }
    }
}

The nanoseconds thing was in response to your suggestion of declaring the variables in one go, at the top of the function. Using allClasses also in setting the new class(es) simply breaks the script. Again, just try it out.

Sticking your variables at the top of the function albeit not set in stone is a recommended practice. Put code through JSLint for example and it will pick up on this.

It ties in nicely with variable hoisting. Again in my opinion it also helps with legibility.

The main one for me with your loop was the fact you are repeatedly calling the RegExp constructor so that it can parse the same string.

Yes we are talking milliseconds, but it’s not necessary. If it doesn’t benefit why bother? In addition I maybe off here, but javascript is asynchronous. If you have other events going on for instance setIntervals which rely on a break in the code to make their callbacks this sort of practice isn’t going to help. Just thoughts Frank that’s all.