Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

JavaScript

Problems with event delegation

Hello,

I've been reading Learning jQuery and have come across a problem in one of the examples.

I have the following HTML code, which represents a style switcher for a page:

<div id="switcher" class="switcher">
        <h3>Style Switcher</h3>
        <button id="switcher-default">
          Default
        </button>
        <button id="switcher-narrow">
          Narrow Column
        </button>
        <button id="switcher-large">
          Large Print
        </button>
      </div>
</div>

I'm trying to use jQuery to apply the rules to the switcher and have come up with the following code on my own, using event delegation:

$(document).ready(function() {
    $(document).ready(function() {
        $("#switcher-default").addClass('selected');
        $('#switcher').on('click', 'button', function() {
            $('body').removeClass();
            $('#switcher button').removeClass('selected');
            $(this).addClass('selected');
        });
        $("#switcher-narrow").on('click', function() {
            $('body').addClass('narrow');
        });
        $("#switcher-large").on('click', function() {
            $('body').addClass('large');
        });
    });
});

This code, however, doesn't work. The classes narrow or large are never applied and I can't seem to understand why.

The only difference in the code the book provides is that it doesn't use event delegation:

$(document).ready(function() {
    $(document).ready(function() {
        $("#switcher-default").addClass('selected');
        $('#switcher button').on('click', function() {
            $('body').removeClass();
            $('#switcher button').removeClass('selected');
            $(this).addClass('selected');
        });
        $("#switcher-narrow").on('click', function() {
            $('body').addClass('narrow');
        });
        $("#switcher-large").on('click', function() {
            $('body').addClass('large');
        });
    });
});

This works perfectly.

Could anybody help me with that?

Thanks in advance!

Chris Dziewa
Chris Dziewa
17,781 Points

Could you please post the stylesheet and the full html page code you have? Out of context this is a bit confusing. Also, you don't actually need two document load functions for the code you are using. In fact it only is slowing down your load times.

4 Answers

Since Chris has already posted solutions I'll try to explain why your code didn't work out.

By adding event delegation you've changed the behaviour of the code. Click handlers are not going to be executed in the same order as they were without event delegation. This code as written is dependent upon that first click handler (the one that removes the body class) to be executed first.

If we first take a look at the book code, there's a total of 5 click handlers assigned to the buttons. The first click handler is assigned to all 3 buttons and then the narrow and large button each get one more click handler assigned to it. So those two buttons will each have two click handlers. Since these click handlers are all assigned to the buttons they will be executed in source order. So we've met the requirement that the first click handler in the code is executed first.

Using event delegation, there's now 3 click handlers instead of 5. #switcher is assigned a click handler and the 2nd and 3rd buttons each get a click handler. Now, when a button is clicked, any click handlers assigned to the buttons will be executed first (in source order if there's more than one). So at this moment the correct body class is set. But then the event bubbles up to #switcher and its click handler gets executed. So the body class ends up getting removed. The code involving the "selected" class still works as expected.

I hope I have that right.

Chris Dziewa
Chris Dziewa
17,781 Points

Nice explanation! I thought it had to do with event bubbling, unfortunately there wasn't much documentation I could find on it. I'm glad I played around with the code though because I found that out by experimentation. Thanks!

Hi Chris,

Thank you!

I relied on this page from the docs: https://api.jquery.com/on/ and my own experimentation as well.

Chris Dziewa
Chris Dziewa
17,781 Points

Wow, sorry that took a while ha. I found two ways to do this. For some reason, while using delegation and non-delegation to change the same element (the body class), the anonymous function with delegation had the last say. It was removing the class as it was being added. You can see this in Chrome developer tools by watching the body tag when you click. It highlights but nothing happens. Removing the .removeClass() method solved the problem of the classes not being added but then they were stuck there. Comments are in the code blocks or you.

This is a way similar to yours and the next one I will show you is much more DRY (Don't Repeat Yourself) code:

$(document).ready(function() {
        $("#switcher-default").addClass('selected');
        $('#switcher').on('click', 'button', function() {
            $('body').removeClass();
            $('#switcher button').removeClass('selected');
            $(this).addClass('selected');
        });
        $("#switcher").on('click', "#switcher-narrow", function() { //delegation here & switched selectors
            $('body').addClass('narrow');
        });
        $("#switcher").on('click', '#switcher-large', function() {   //Used delegation here as well
            $('body').addClass('large');
        });
    });

Now for the code I wrote that is much more DRY. This was created by adding data attributes to the buttons in the html which I will include after. Notice I didn't use $(document).ready() at all. This is because I moved the jquery and js scripts to the very bottom of the html document before the closing body tag. This will make you code faster. Just remember to not put any other elements below the scripts (jquery should be first as usual).

$("#switcher-default").addClass('selected');
        $('#switcher').on('click', 'button', function() {

            var style = $(this).data('class');  // Saves data-class info from the current button in var style

            $('body').attr('class', style);     //Replaces the body class with the style variable
            $('#switcher button').removeClass('selected');
            $(this).addClass('selected');
        });
<div id="switcher" class="switcher">
        <h3>Style Switcher</h3>
        <button id="switcher-default" data-class="">    
          Default
        </button>
        <button id="switcher-narrow" data-class="narrow">
          Narrow Column
        </button>
        <button id="switcher-large" data-class="large">
          Large Print
        </button>
      </div>

Hope this helps!

Hey Chris,

I think your first solution is about the minimal amount of changes needed to the book code to get it to work with delegation. All other solutions I looked at involved code duplication and additional logic to make things work out right. By adding delegation to all, the click handlers once again execute in source order like the book code.

Nice second solution! I feel it's an improvement over the book code. It's shorter and removes the repetitiveness of setting up the three click handlers. You managed to get it down to 1 click handler.

Chris Dziewa
Chris Dziewa
17,781 Points

Thank Jason! I spent a while trying to get it to work and then when I did, I was thinking that I really didn't like all the repeated code so I did some experimenting.

Here is the HTML code:

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>A Christmas Carol</title>

    <link rel="stylesheet" href="03.css" type="text/css" />

    <script src="jquery.js"></script>
    <script src="03.js"></script>
  </head>
  <body>
    <div id="container">

      <div id="switcher" class="switcher">
        <h3>Style Switcher</h3>
        <button id="switcher-default">
          Default
        </button>
        <button id="switcher-narrow">
          Narrow Column
        </button>
        <button id="switcher-large">
          Large Print
        </button>
      </div>

      <div id="header">
        <h2>A Christmas Carol</h2>
        <h2 class="subtitle">In Prose, Being a Ghost Story of Christmas</h2>
        <div class="author">by Charles Dickens</div>
      </div>

      <div class="chapter" id="chapter-preface">
        <h3 class="chapter-title">Preface</h3>
        <p>I HAVE endeavoured in this Ghostly little book, to raise the Ghost of an Idea, which shall not put my readers out of humour with themselves, with each other, with the season, or with me.  May it haunt their houses pleasantly, and no one wish to lay it.</p>
        <p>Their faithful Friend and Servant,</p>
        <p>C. D.</p>
        <p>December, 1843.</p>
      </div>

      <div class="chapter" id="chapter-1">
        <h3 class="chapter-title">Stave I: Marley's Ghost</h3>
        <p>MARLEY was dead: to begin with. There is no doubt whatever about that. The register of his burial was signed by the clergyman, the clerk, the undertaker, and the chief mourner. Scrooge signed it: and Scrooge's name was good upon 'Change, for anything he chose to put his hand to. Old Marley was as dead as a door-nail.</p>
        <p>Mind! I don't mean to say that I know, of my own knowledge, what there is particularly dead about a door-nail. I might have been inclined, myself, to regard a coffin-nail as the deadest piece of ironmongery in the trade. But the wisdom of our ancestors is in the simile; and my unhallowed hands shall not disturb it, or the Country's done for. You will therefore permit me to repeat, emphatically, that Marley was as dead as a door-nail.</p>
        <p>Scrooge knew he was dead? Of course he did. How could it be otherwise? Scrooge and he were partners for I don't know how many years. Scrooge was his sole executor, his sole administrator, his sole assign, his sole residuary legatee, his sole friend, and sole mourner. And even Scrooge was not so dreadfully cut up by the sad event, but that he was an excellent man of business on the very day of the funeral, and solemnised it with an undoubted bargain.</p>
        <p>The mention of Marley's funeral brings me back to the point I started from. There is no doubt that Marley was dead. This must be distinctly understood, or nothing wonderful can come of the story I am going to relate. If we were not perfectly convinced that Hamlet's Father died before the play began, there would be nothing more remarkable in his taking a stroll at night, in an easterly wind, upon his own ramparts, than there would be in any other middle-aged gentleman rashly turning out after dark in a breezy spot&mdash;say Saint Paul's Churchyard for instance&mdash; literally to astonish his son's weak mind.</p>
        <p>Scrooge never painted out Old Marley's name. There it stood, years afterwards, above the warehouse door: Scrooge and Marley. The firm was known as Scrooge and Marley. Sometimes people new to the business called Scrooge Scrooge, and sometimes Marley, but he answered to both names. It was all the same to him.</p>
        <p>Oh! But he was a tight-fisted hand at the grind-stone, Scrooge! a squeezing, wrenching, grasping, scraping, clutching, covetous, old sinner! Hard and sharp as flint, from which no steel had ever struck out generous fire; secret, and self-contained, and solitary as an oyster. The cold within him froze his old features, nipped his pointed nose, shrivelled his cheek, stiffened his gait; made his eyes red, his thin lips blue; and spoke out shrewdly in his grating voice. A frosty rime was on his head, and on his eyebrows, and his wiry chin. He carried his own low temperature always about with him; he iced his office in the dog-days; and didn't thaw it one degree at Christmas.</p>
        <p>External heat and cold had little influence on Scrooge. No warmth could warm, no wintry weather chill him. No wind that blew was bitterer than he, no falling snow was more intent upon its purpose, no pelting rain less open to entreaty. Foul weather didn't know where to have him. The heaviest rain, and snow, and hail, and sleet, could boast of the advantage over him in only one respect. They often "came down" handsomely, and Scrooge never did.</p>
        <p>Nobody ever stopped him in the street to say, with gladsome looks, "My dear Scrooge, how are you? When will you come to see me?" No beggars implored him to bestow a trifle, no children asked him what it was o'clock, no man or woman ever once in all his life inquired the way to such and such a place, of Scrooge. Even the blind men's dogs appeared to know him; and when they saw him coming on, would tug their owners into doorways and up courts; and then would wag their tails as though they said, "No eye at all is better than an evil eye, dark master!"</p>
        <p>But what did Scrooge care! It was the very thing he liked. To edge his way along the crowded paths of life, warning all human sympathy to keep its distance, was what the knowing ones call "nuts" to Scrooge.</p>
        </div>
    </div>
  </body>
</html>

Here is the CSS:

/***************************************
   Default Styles
************************************** */

html, body {
  margin: 0;
  padding: 0;
}

body {
  font: 62.5% Verdana, Helvetica, Arial, sans-serif;
  color: #000;
  background: #fff;
}
#container {
  font-size: 1.2em;
  margin: 10px 2em;
}

h1 {
  font-size: 2.5em;
  margin-bottom: 0;
}

h2 {
  font-size: 1.3em;
  margin-bottom: .5em;
}
h3 {
  font-size: 1.1em;
  margin-bottom: 0;
}

code {
  font-size: 1.2em;
}

a {
  color: #06581f;
}


/***************************************
   Chapter Styles
************************************** */

.poem {
  margin: 0 5em;
}
.chapter {
  margin: 1em;
}
.switcher {
  float: right;
  background-color: #ddd;
  border: 1px solid #000;
  margin: 10px;
  padding: 10px;
  font-size: .9em;
}
.switcher h3 {
  margin: .5em 0;
}

#header {
  clear: both;
}

body.large .chapter {
  font-size: 1.5em;
}

body.narrow .chapter {
  width: 250px;
}

.selected {
  font-weight: bold;
}

.hidden {
  display: none;
}

.hover {
  cursor: pointer;
  background-color: #afa;
}

As for the two document load functions, that was mistake. Thank you! :)

Wow, thanks for you answer, guys!

Jason, your explanation was incredibly clear! Thanks a lot! Event bubbling seems to be something that can really confuse a beginner, like me. I guess I will have a good grasp on this concept only after a lot of practice!

Chris, thanks a lot for that DRY code example! It's always great to see how you guys make things seem so much simpler! :P

You're welcome, Felipe!

I realize that you probably were just using this example as a way to learn about delegation but I think according to the docs it's not really a good use case for delegation. My take on it is that you don't really need to use delegation if you're not going to benefit much from it.

https://api.jquery.com/on/

The docs mention two advantages with delegation. One is that it will still work for descendant elements added at a later time. So in your example, if you were going to add more buttons dynamically later on then the click handler will still work.

Second, is lower overhead. The example they give is a 1000 row table. You can either assign 1000 click handlers to the rows or have one click handler assigned to the <tbody> using delegation.

With the solutions on this page we're only talking about 1, 3, or 5 click handlers and so it's probably not a significant performance difference with whichever one you use.

I'm not sure if professional jQuery developers would always use delegation or only use it when they're in one of those two situations.

Hey, Jason!

I was really just experimenting with delegation. The book didn't even recommend using it at that point. :P

What I take from this is that using delegation can add a little bit of complexity to the code, since I have to be careful with what event is triggered first, because of bubbling.

Chris Dziewa
Chris Dziewa
17,781 Points

Anytime! Experimenting with jQuery is a lot of fun. Thanks for posting a nice question!