0

I need to trigger an event on a class when that class changes The only known change noticed in the DOM is that the class obtains a second class (say the class is "selectable", it becomes "selectable selected")

https://jsfiddle.net/zn1xj7wb/1/

In this fiddle, the blue squares may be selected and the css change happens when the class changes (adds "selected") The goal is to be able to do something in another part of my code like that:

$("[class*='selectable']").on('classChange', function() {
    //do stuff like change the background color or add text
    //alert("this selectable div has been selected");      
});

I am unsure how to proceed as jquery has no event for a class change, and I cannot add "classChange" the trigger to the hidden part of the code that adds and removes the "selected" class for it to be picked up by my code.

EDIT: the reason I need the trigger to be the class change is that it is a graph that uses up the first click to change the class (select the node of the graph) and so a first click on the div of that class does not register, only the second time, and I cannot have to click twice to //do stuff.

MorganFR
  • 331
  • 3
  • 19
  • You might consider a [Mutation Observer](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). See [How to fire an event on class change using jQuery?](https://stackoverflow.com/questions/19401633/how-to-fire-an-event-on-class-change-using-jquery) – showdev Oct 04 '18 at 16:35
  • 1
    Possible duplicate of [How to fire an event on class change using jQuery?](https://stackoverflow.com/questions/19401633/how-to-fire-an-event-on-class-change-using-jquery) – Taplar Oct 04 '18 at 16:44
  • @Taplar unfortunately, I cannot use something like $('#myDiv').addClass('submission-ok').trigger('classChange'); doesn't work as I do not have access to that part of the code that adds the class in the first place. I will have to look into the mutations but i'm afraid i'll have a similar issue. – MorganFR Oct 04 '18 at 17:32

2 Answers2

0

EDIT: Okay, so that won't work (see comments). However, I was able to come up with another solution. While you could regularly scan the whole DOM for changes using an external library, in this instance, you can make the app more performant by limiting your scope to just the selectable items.

What the following code does (jsfiddle link below) is take an initial sampling of the selected elements on the page. Then, once per event loop, it re-samples those selected elements. For each element that wasn't there before, it triggers a custom event:

$(document).ready(function() {
  $('.selectable').on('customSelectEvent', (e) =>{
    console.log("hello, world!");
    // Do your stuff here
  });
  // Get the starting list of selectable elements
  var selecteds = $('.selected');
  // Using setInterval to make sure this runs at the back of the event loop
  setInterval(() => {
    let loopSelecteds = $('.selected');
    $.each(loopSelecteds, function(loopIndex, loopSelected) {
      let alreadySelected = false;
      $.each(selecteds, function(index, selected) {
        if ($(selected).get(0) === $(loopSelected).get(0)) {
          alreadySelected = true;
        }
      });
      if (!alreadySelected) {
        $(loopSelected).trigger('customSelectEvent');
      }
    });
    selecteds = loopSelecteds;
  }, 0);
})

Some things to note here:

  • setInterval(()=>{...}, 0) is being used to cast this operation to the back of the event loop, so it will evaluate once per turn. Use caution when doing this, because if you do it too much, it can impact performance.
  • $().get(0) === $().get(0) is testing the DOM elements to see if they are the same element. We don't want to trigger the event if they are. Credit: https://stackoverflow.com/a/19546658/10430668
  • I'm using $.each() here because it's intelligent enough to handle collections of jQuery objects, which other loops weren't (without some fiddling).
  • Someone spot check me on this, but you may be able to put the custom event listener elsewhere in the code.

JS Fiddle: https://jsfiddle.net/zn1xj7wb/15/

This is my first answer, which doesn't work in this use case. I'll include it so that users who aren't so stuck can benefit from it:

Is there any reason you can't bind another listener to the click event and test if it's got the right class? Such as:

$(document).ready(function() {
    $(".selectable").click((e) => {
        const currentElement = $(e.currentTarget);
        // This is a little tricky: don't run the code if it has the class pre-setTimeout()
        if (currentElement.hasClass('selected')) {
            return;
        }
        // Using setTimeout to cast the evaluation to the end of the event loop
        setTimeout(()=>{
             if (currentElement.hasClass('selected')) {
                 // Do your things here.
                 alert("selected!");
             }
        },0);
    })
})
Jpec07
  • 818
  • 6
  • 8
  • Unfortunately, this will not work for me as the first click is used by the rest of the hidden code to add the .selected class, This solution would require a second click for me. This is why the trigger has to be the class change and not something else like a click and check. – MorganFR Oct 04 '18 at 17:30
  • It wouldn't require a second click. A cool thing about JavaScript is that it lets you bind multiple listeners to a single event. While it's generally a good idea to try and keep all your listeners in one spot, when you're dealing with a sealed codebase, this lets you work around it pretty handily. See https://jsfiddle.net/zn1xj7wb/12/ – Jpec07 Oct 04 '18 at 17:36
  • In my case, the first click does not register as a click at all even if I use an onClick listener on a specific div/class, in fact, it does not register as a click in the at all. It is used up by some part of the code somewhere I have no access to. The trigger really needs to be the change of class and not a click. – MorganFR Oct 04 '18 at 17:40
  • Hm. That is a bit of a pickle. It sounds like your sealed code is calling an `event.stopPropagation()` somewhere, which is annoying when you didn't put it there yourself. Give me a couple minutes - I have an idea, but need to make sure it will work, first. – Jpec07 Oct 04 '18 at 17:45
  • Just updated my answer! Was a bit of a headache to re-learn how to iterate over jQuery collections, but this should help. :) – Jpec07 Oct 04 '18 at 20:01
0

I'm not sure I understand your problem, but what I would do is atach the event to the document, like this:

$(document).on("click",".selectable", function() {
//do your stuff here
});

Now, as I've read you need to do something right after you add the class "selected" to "selectable", so you could do it in the function by checking wether it has the class or not and then do your stuff after you add the class "selected".

$(document).on("click",".selectable", function() {
  if($(this).hasClass("selected")){
        $(this).removeClass("selected")
        //do your stuff
  }else{
       $(this).addClass("selected")
       //do some different stuff
  }
});