102

I would like to have something like:

$('#myDiv').bind('class "submission ok" added'){
    alert('class "submission ok" has been added');
});
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Matoeil
  • 6,851
  • 11
  • 54
  • 77

4 Answers4

211

There is no event raised when a class changes. The alternative is to manually raise an event when you programatically change the class:

$someElement.on('event', function() {
    $('#myDiv').addClass('submission-ok').trigger('classChange');
});

// in another js file, far, far away
$('#myDiv').on('classChange', function() {
     // do stuff
});

UPDATE - 2015

This question seems to be gathering some visitors, so here is an update with an approach which can be used without having to modify existing code using the new MutationObserver:

var $div = $("#foo");
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    var attributeValue = $(mutation.target).prop(mutation.attributeName);
    console.log("Class attribute changed to:", attributeValue);
  });
});

observer.observe($div[0], {
  attributes: true,
  attributeFilter: ['class']
});

$div.addClass('red');
.red {
  color: #C00;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="foo" class="bar">#foo.bar</div>

Be aware that the MutationObserver is only available for newer browsers, specifically Chrome 26, FF 14, IE 11, Opera 15 and Safari 6. See MDN for more details. If you need to support legacy browsers then you will need to use the method I outlined in my first example.


UPDATE - 2022

Here's an implementation of the above wrapped in to a jQuery extension method:

// extension method:
$.fn.onClassChange = function(cb) {
  return $(this).each((_, el) => {
    new MutationObserver(mutations => {
      mutations.forEach(mutation => cb && cb(mutation.target, mutation.target.className));
    }).observe(el, {
      attributes: true,
      attributeFilter: ['class'] // only listen for class attribute changes 
    });
  });
}

// usage:
const $foo = $("#foo").onClassChange((el, newClass) => console.log(`#${el.id} had its class updated to: ${newClass}`));
const $fizz = $("#fizz").onClassChange((el, newClass) => console.log(`#${el.id} had its class updated to: ${newClass}`));

// trigger
$('#trigger').on('click', () => {
  $foo.removeClass('red');
  $fizz.addClass('green dark-bg');
});
.red {
  color: #C00;
}

.green {
  color: #0C0;
}

.dark-bg {
  background-color: #666;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<button id="trigger">Change classes</button>
<div id="foo" class="bar red">#foo.bar</div>
<div id="fizz" class="buzz">#fizz.buzz</div>
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
  • In completion of this answer, i found this : https://stackoverflow.com/a/1950052/1579667 – Benj Oct 27 '17 at 12:25
  • 2
    @Benj That's the same as my original answer (ie. using `trigger()`) although note that it's very outdated due to the use of `bind()`. I'm not sure how it 'completes' anything, though – Rory McCrossan Feb 15 '18 at 13:53
  • In my case I needed to fire an event when a dom node has a specific class `MutationObserver` works for me – Mario Mar 21 '19 at 16:24
  • 2
    This is a improved form of this solution with filter https://stackoverflow.com/a/42121795/12074818 – MVDeveloper1 Aug 19 '20 at 12:44
24

You could replace the original jQuery addClass and removeClass functions with your own that would call the original functions and then trigger a custom event. (Using a self-invoking anonymous function to contain the original function reference)

(function( func ) {
    $.fn.addClass = function() { // replace the existing function on $.fn
        func.apply( this, arguments ); // invoke the original function
        this.trigger('classChanged'); // trigger the custom event
        return this; // retain jQuery chainability
    }
})($.fn.addClass); // pass the original function as an argument

(function( func ) {
    $.fn.removeClass = function() {
        func.apply( this, arguments );
        this.trigger('classChanged');
        return this;
    }
})($.fn.removeClass);

Then the rest of your code would be as simple as you'd expect.

$(selector).on('classChanged', function(){ /*...*/ });

Update:

JS Fiddle Demo

This approach does make the assumption that the classes will only be changed via the jQuery addClass and removeClass methods. If classes are modified in other ways (such as direct manipulation of the class attribute through the DOM element) use of something like MutationObservers as explained in the accepted answer here would be necessary.

Also as a couple improvements to these methods:

  • Trigger an event for each class being added (classAdded) or removed (classRemoved) with the specific class passed as an argument to the callback function and only triggered if the particular class was actually added (not present previously) or removed (was present previously)

  • Only trigger classChanged if any classes are actually changed

      (function( func ) {
          $.fn.addClass = function(n) { // replace the existing function on $.fn
              this.each(function(i) { // for each element in the collection
                  var $this = $(this); // 'this' is DOM element in this context
                  var prevClasses = this.getAttribute('class'); // note its original classes
                  var classNames = $.isFunction(n) ? n(i, prevClasses) : n.toString(); // retain function-type argument support
                  $.each(classNames.split(/\s+/), function(index, className) { // allow for multiple classes being added
                      if( !$this.hasClass(className) ) { // only when the class is not already present
                          func.call( $this, className ); // invoke the original function to add the class
                          $this.trigger('classAdded', className); // trigger a classAdded event
                      }
                  });
                  if( prevClasses != this.getAttribute('class') ) $this.trigger('classChanged'); // trigger the classChanged event
              });
              return this; // retain jQuery chainability
          }
      })($.fn.addClass); // pass the original function as an argument
    
      (function( func ) {
          $.fn.removeClass = function(n) {
              this.each(function(i) {
                  var $this = $(this);
                  var prevClasses = this.getAttribute('class');
                  var classNames = $.isFunction(n) ? n(i, prevClasses) : n.toString();
                  $.each(classNames.split(/\s+/), function(index, className) {
                      if( $this.hasClass(className) ) {
                          func.call( $this, className );
                          $this.trigger('classRemoved', className);
                      }
                  });
                  if( prevClasses != this.getAttribute('class') ) $this.trigger('classChanged');
              });
              return this;
          }
      })($.fn.removeClass);
    

With these replacement functions you can then handle any class changed via classChanged or specific classes being added or removed by checking the argument to the callback function:

$(document).on('classAdded', '#myElement', function(event, className) {
    if(className == "something") { /* do something */ }
});
Nickolas Hook
  • 774
  • 5
  • 13
  • 1
    This works fine, but in the "rest of the code" the event handler should be delegated to the target selector to allow binding to new elements: `$(parent_selector).on('classChanged', target_selector, function(){ /*...*/ });`. Took me a while to see why my dynamically created elements didn't fire the handler. See http://learn.jquery.com/events/event-delegation/ – Timm Oct 24 '15 at 09:42
10

Use trigger to fire your own event. When ever you change class add trigger with name

JS Fiddle DEMO

$("#main").on('click', function () {
    $("#chld").addClass("bgcolorRed").trigger("cssFontSet");
});

$('#chld').on('cssFontSet', function () {

    alert("Red bg set ");
});
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Satinder singh
  • 10,100
  • 16
  • 60
  • 102
2

you can use something like this:

$(this).addClass('someClass');

$(Selector).trigger('ClassChanged')

$(otherSelector).bind('ClassChanged', data, function(){//stuff });

but otherwise, no, there's no predefined function to fire an event when a class changes.

Read more about triggers here

Deep Sharma
  • 3,374
  • 3
  • 29
  • 49
  • 1
    The handler of the event must be the same item that trigger the event. $(this).addClass('someClass'); $(Selector).trigger('ClassChanged') //It must be the same Selector $(Selector).bind('ClassChanged', data, function(){//stuff }); – Meko Perez Estevez Oct 16 '13 at 11:06
  • 3
    Seems you have copied from here , if yes then good if you provide link too http://stackoverflow.com/questions/1950038/jquery-fire-event-if-css-class-changed/1950052#1950052 – Satinder singh Oct 16 '13 at 11:14