Paul L
Paul L

Reputation: 938

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>

Upvotes: 0

Views: 129

Answers (3)

Aparna
Aparna

Reputation: 255

Yeah, you need to use event delegations by using 'on click' events.
For your information, delegated event is different than direct event.

When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.

Upvotes: 0

Rajaprabhu Aravindasamy
Rajaprabhu Aravindasamy

Reputation: 67217

You have to use event-delegation at this context,

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

DEMO

Event would be bound only once in the beginning based on the selector. Event handlers will not be triggered depends on the set of elements available in the DOM during click invocation. If you want to do it in the way that you wanted, then you have to use event delegation.

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1075785

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.

Upvotes: 1

Related Questions