'How to use addClass and removeClass repeatedly on a single element?

So what I want to achieve is just change the classes of a HTML link on every click like this:

  • Remove .first class if it is present, then add .second class
  • Remove .second class if it is present, then add .third class
  • Remove .third class if it is present, then add .fourth class
  • And so forth...

No luck so far. What could I be doing wrong?

Here's the single line of HTML code where I'm trying my jQuery code on:

<a class="first" href="#">Test 1</a>

Here's my jQuery:

$( "#menu li a.first" ).click(function() {

   $( "#menu li a.first" ).removeClass("first").addClass("second");

}

$( "#menu li a.second" ).click(function() {

   $( "#menu li a.second" ).removeClass("second").addClass("third");

}

$( "#menu li a.third" ).click(function() {

   $( "#menu li a.second" ).removeClass("third").addClass("fourth");

}

Thanks in advance!



Solution 1:[1]

The problem is you're trying to attach the event handler before it even has the class second or third.

Besides this approach is pretty verbose. I suggest simply providing an array of classes. Like so:

var classNames = ['first', 'second', 'third'];

Then add a different identifier to the button, for instance add a class class-changer. And attach the following event handler.

$('.class-changer').on('click', function() {
    var $el = $(this)
    for (var i= 0; i < classNames.length; i++) {
        if ($el.hasClass(classNames[i]) && classNames[i+1]) {
           $el.removeClass(classNames[i]).addClass(classNames[i+1]);
           break;
        }
    }
});

Solution 2:[2]

Put all classes in an array and on click of the link add class one by one like following.

var classes = ["first", "second", "third", "fourth"];
$("#menu li a").click(function () {
    var index = classes.indexOf(this.className);
    var newIndex = (index + 1) % classes.length; //return to first after reaching last

    $(this).removeClass(classes[index]).addClass(classes[newIndex]);
});
.first { color: red; }
.second { color: green; }
.third { color: blue; }
.fourth { color: purple; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="menu">
    <li>
        <a class="first" href="#">Test 1</a>
    </li>
</ul>

Solution 3:[3]

Assuming you actually only have 1 link whose state you're trying to change, instead of a bunch of links in your menu that you want to ALL be moved from ".first" to ".second" when one is clicked, I would suggest this as the most idiomatic way (pun not intended).

// Only select the menu once
var $menu = $('#menu');

// Delegate to elements with the correct class.
// Specifying the "li a" is probably unnecessary,
// unless you have other elements with the same classes in "#menu".
$menu.on('click', '.first', function(e) {

    // Inside a jQuery event handler,
    // `this` refers to the element that triggered the event.
    // If the event is delegated, it's the delegation target
    // (".first" in this instance), not the bound element ("#menu").
    $(this).removeClass('first').addClass('second');

});

$menu.on('click', '.second', function(e) {
   $(this).removeClass('second').addClass('third');
});

$menu.on('click', '.third', function(e) {
   $(this).removeClass('third').addClass('fourth');
});

Resources:

Solution 4:[4]

You can do it with the usage of .data()

HTML:

<a class="first" href="#" id="test">Test 1</a>

JS:

$(".first").data("classes",["one","two","three","four"]).click(function() {
  var elem = $(this);
  var cnt = (elem.data("cnt") || 0)
  var classes = elem.data("classes");
  elem.removeClass().addClass(classes[cnt % classes.length] + " first").data("cnt",++cnt);
});

Demo

$(".first").data("classes",["one","two","three","four"]).click(function() {
  var elem = $(this);
  var cnt = (elem.data("cnt") || 0)
  var classes = elem.data("classes");
  elem.removeClass().addClass(classes[cnt % classes.length] + " first").data("cnt",++cnt);
});
.one{
  color:red;
}
.two{
  color:yellow;
}
.three{
  color:green;
}
.four{
  color:blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<a class="first" href="#" id="test">Test 1</a>

Solution 5:[5]

Not sure if this would solve your issue but I would shoot for a conditional statement and only one delegated event listener:

$("#menu li").on("click", "a", function () {
  if ($(this).hasClass("first")) {
    $(this).removeClass("first").addClass("second");
  } else if ($(this).hasClass("second")) {
    $(this).removeClass("second").addClass("third");
  }
  // etc...
});

Solution 6:[6]

If you want to bind an event the selected element must exist previously.

To bind an event handler to elements that does not yet exist (ex. dynamically created or modified) you can do this:

$(document).on('click', '#menu li a.first', function() {

    $( "#menu li a.first" ).removeClass("first").addClass("second");

});

$(document).on('click', '#menu li a.second', function() {

   $( "#menu li a.second" ).removeClass("second").addClass("third");

});

$(document).on('click', '#menu li a.third', function() {

   $( "#menu li a.third" ).removeClass("third").addClass("fourth");

});

Solution 7:[7]

<a class="changable first" href="#">Test 1</a>
$( ".changable" ).click(function(event) {
classes = ['first','second','third','fourth']
changed=false
for (c in classes){
    if (event.target.classList.contains(classes[c]) && changed==false){
       $(this).removeClass((classes[c]));
       index_to_add=classes.indexOf(classes[c])+1
       class_to_add=classes[index_to_add]
       $(this).addClass(class_to_add);
       changed=true;
    }
}

});

Solution 8:[8]

Okay so there is a few workaround for this, which wasn't mentioned yet.

You can use Javascript object for this not just array. Object could make it easier if you want a chain instead of list.

var classNames = {first:'second', second:'third', third:'fourth'};

$('#menu li a').on('click', function() {
    if(typeof classNames[this.className] !== 'undefined'){
        this.className = classNames[this.className];
    }
});

Second method is to use .on('click', [selector], handler) instead click which can handle dynamicly loaded, added or changed elements.

$('#menu li').on('click', 'a.first', function() {
    $(this).removeClass("first").addClass("second");
});

$('#menu li').on('click', 'a.second', function() {
   $(this).removeClass("second").addClass("third");
});

$('#menu li').on('click', 'a.third', function() {
   $(this).removeClass("third").addClass("fourth");
});

Not even close to perfect but still a working solution.

You can use if .. else or switch .. case inside a function to create a decision tree.

So basically there is a lot of solution. Pick the best.

Solution 9:[9]

Try binding event to parent,

My try,

var $classes = ['first', 'second', 'third'];

$(function(){
    $('#subject').click(function(){
        current = $(this).find('a:first');
        index = $.inArray(current.attr('class'), $classes);
        if($classes.length > index+1)
            current.removeClass($classes[index]).addClass($classes[index+1])
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id='subject'>
	<a class="first" href="#">Test 1</a>
</div>

Solution 10:[10]

No, you can't. As JavaScript only runs after the page loads ( if you put them inside the $( document ).ready() function ), further functions down below will never be executed. It can only detect the <a class="first" href="#">Test 1</a> but not the <a class="second" href="#">Test 1</a> because the <a class="second" href="#">Test 1</a> are generated after the page loads and, therefore, will never be executed, unless you are using Ajax.

Update: This can be done. Please see @i3b13's comment below.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 ANeves
Solution 3 iabw
Solution 4 wizzwizz4
Solution 5 UnrealApex
Solution 6 Daniel
Solution 7 Dmitry Yudin
Solution 8 Nergal
Solution 9 viral
Solution 10