rampion
rampion

Reputation: 89113

jQuery: Rotating elements through a set of classes

toggleClass is great if I just have two presentations styles I want to use, but a lot of times I've got more than two states that I want to use.

For example, if I want to rotate through three classes:

if ($(this).hasClass("A"))
   $(this).removeClass("A").addClass("B");
else if ($(this).hasClass("B"))
   $(this).removeClass("B").addClass("C");
else if ($(this).hasClass("C"))
   $(this).removeClass("C").addClass("A");

I'd like to have a simple method that did this for me, something like:

$(this).rotateClass('A', 'B', 'C');

Is there an existing jQuery method or plugin that does this?

Upvotes: 4

Views: 1232

Answers (6)

Mahn
Mahn

Reputation: 16605

Here's what I use:

$.fn.rotateClass = function(classes) {
    var $set = this.each(function () {
        var $this = $(this);
        var index = (($this.data("currentClassIndex")) ? $this.data("currentClassIndex") : 0) % classes.length;

        $this.removeClass(classes.join(" "))
             .addClass(classes[index])
             .data("currentClassIndex", ++index);
    });

    return $set;
}

No for loops, no hasClass calls, compatible with sets of elements, very compact and very performant.

Usage:

$("#randomDiv").rotateClass(["classA", "classB", "classC", "classD"]);

Upvotes: 1

aziz punjani
aziz punjani

Reputation: 25786

Here's my attempt at it.

  $.fn.rotate= function( classes ) {
      $element = this;  // element the rotate is being applied to
      for( var i = 0; i <  classes.length; i++ ){   // look through the classes array
          if( $element.hasClass( classes[i] ) ){  // check if current element has the class specified in the array 
              $element.removeClass( classes[i] );  // if it does then remove the class 
              $element.addClass( classes[ ++i % classes.length ] );  
              return $element; // return the current element so other functions can be applied to it. 
           }
      }
  }

You would use it by calling .rotate on the element and passing in an array of classes to rotate.

Here's an interactive demo. I just added the animate to show that you can apply other jQuery functions to the element after the rotate is finished.

 $('#test').rotate(['A','B','C']); 

The reason we use the modulus operator is, if i > the length of the array then we want the correct index. E.g array of classes is ['A','B','C'], if the current class on our element is 'C' then the index of the current class is 2 (arrays are 0 index based), then if we increment 2 + 1 = 3 to get the class we want to rotate it to but we don't have any class at index 3 hence 3 % 3  = 0 which is the index of 'A', that's what we want, hence the modulus will get us the correct index every time. 

Upvotes: 1

rampion
rampion

Reputation: 89113

My current solution:

$.fn.mapClasses = function(mapping){
  return this.each(function(){
    this.className = this.className.split(/\s+/).map(function(klass){
      return mapping[klass] || klass
    }).join(' ');
  });
};
$.fn.rotateClasses(){
  var mapping = {};
  var prev = arguments[0];
  for (var i = arguments.length - 1; i >= 0; i--){
    mapping[ arguments[i] ] = prev;
    prev = arguments[i];
  }
  return this.mapClasses(mapping);
};

Upvotes: 0

Richard Dalton
Richard Dalton

Reputation: 35793

You can still use toggleClass and pass it a function instead:

$(this).toggleClass(function() {
    if ($(this).hasClass('red')) {
      return 'green red';
    }

    if ($(this).hasClass('green')) {
      return 'blue green';
    }

    if ($(this).hasClass('blue')) {
      return 'red blue';
    }
});

Not the easiest code to maintain though, so it would probably be better to wrap it in a plugin.

http://jsbin.com/inaduy/edit#javascript,html,live

And now made in to a very basic plugin (that needs a bit of validation). You just pass an array of objects containing pairs of classes to swap.

var classArray = [{current: 'red', next:'green'}, {current:'green', next:'blue'}, {current:'blue', next:'red'}];

$('#hello').click(function() {
  $(this).toggleRotate(classArray);
});

(function($) {
  $.fn.toggleRotate = function(classes) {

    return $(this).toggleClass(function() {
      var l = classes.length;
      for (var i = 0; i < l; i++) {
        if ($(this).hasClass(classes[i].current)) {
          return classes[i].current + ' ' + classes[i].next;
        }        
      }
      // If it makes it here return the first current value
      return classes[0].current;
    });
  };
})(jQuery);

http://jsbin.com/inaduy/2/edit

Upvotes: 0

RightSaidFred
RightSaidFred

Reputation: 11327

Put them in an Array:

var arr = ['A', 'B', 'C'];
var i = 0;

Then in the handler:

i = ++i % arr.length;

$(this).toggleClass( this.className + ' ' + arr[ i ] );

The use of this.className assumes that there's not other classes applied to this element.

JSFIDDLE DEMO


If there may be other classes, use this:

$(this).removeClass( arr[ i ] );
i = ++i % arr.length;
$(this).addClass( arr[ i ] );

JSFIDDLE DEMO


If there may be other elements, and each need their own state, you could use a closure to associate a unique i with each element:

$('div').each(function() {
    var i = 0;

    $(this).click(function() {
        $(this).removeClass( arr[ i ] );
        i = ++i % arr.length;
        $(this).addClass( arr[ i ] );
    });
});

JSFIDDLE DEMO

Upvotes: 0

jfriend00
jfriend00

Reputation: 707876

This plugin code will rotate between the passed classes (however many there are (2-n). If none of the passed classes are found on the object, then the first class is set.

$.fn.rotateClass(/* pass multiple class names here */) {
    for (var i = 0; i < arguments.length; i++) {
        if (this.hasClass(arguments[i])) {
            this.removeClass(arguments[i]));
            i++;
            if (i >= arguments.length) {
                i = 0;
            }
            this.addClass(arguments[i]);
            return(this);
        }
    }
    // none found so set the first class
    if (arguments.length > 0) {
        this.addClass(arguments[0]);
    }
    return(this);
}

This version of the code assumes that you are applying the same classes to all the DOM objects in the jQuery object and they all have the same state. It could be extended to treat each DOM object in the jQuery object separately and rotate each ones separately if that was required.

Upvotes: 0

Related Questions