Paul L Paul L - 7 months ago 14
HTML Question

attach click handler based on class, whenever element gains that class

I'm trying to teach myself JQuery, and have run into what I assume is a very basic level problem. What I want to end up with is text that changes styles when it's clicked, switching back and forth between two classes. The first half works fine - when I click the span the first time, the new class gets assigned. But nothing happens when I click the second time. I assume this is because the JQuery selector applies the click handlers exactly one time, to the elements that match the selector at that moment. How can I modify my code so that the click handler assigned to a specific class will be applied to all elements that have that class, regardless of when they gain that class?

Fiddle

<html>
<head>
<title>My JQuery Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
<script type="text/javascript">

$(document).ready(function () {
$(".spoiler").click(function() {
this.className = "revealed";
});
$(".revealed").click(function() {
this.className = "spoiler";
});
})

</script>
<style type="text/css">
.spoiler {
color: black;
background-color: black;
cursor:pointer
}
.revealed {
color: red;
background-color:white;
cursor:pointer
}
</style>
</head>
<body>
<h1>Caution, spoilers ahead</h1>

<p>At the end of <em>Soilent Green</em>, the main character reveals in dramatic fashion that "<span class="spoiler">Soilent Green is people!</span>"</p>

</body>
</html>

Answer

This is a classic use case for event delegation, where you hook the event on a container element (because click bubbles), and then tell jQuery to only trigger your handler if the event travelled through an element that matches a given selector. The check is done when the click occurs, so updating classes happens seamlessly:

$(document).ready(function () {
    $(document.body).on("click", ".spoiler", function() {
        this.className = "revealed";
    });
    $(document.body).on("click", ".revealed", function() {
        this.className = "spoiler";
    });
})

In that code, the container I've used is the page body, but usually there's a container a bit closer to the elements in question that you can use.

More in the documentation for on.

Also note that you can simplify that even further via toggleClass, which adds or removes classes based on whether they're already there, and accepts multiple class names:

$(document).ready(function () {
    $(document.body).on("click", ".spoiler, .revealed", function() {
        $(this).toggleClass("spoiler revealed");
    });
})

Side note: You don't need to use ready if you put your script tags at the end of the HTML, just before the closing </body> tag, which is usually best practice barring a strong reason to do something else.