1

I have around sixty filter buttons in index.html, each of which has a $().click() event handler attached.

$(document).ready(function() {
    $('.filter_button_class').click(function(e) {
        console.log("filter button clicked");
        e.preventDefault();
    });
});

In my CSS file, the :active selector ensures a filter button turns blue when it is clicked.

Now the code works fine, most of the time. When index.html is first loaded in the browser, the event handlers all seem to attach correctly and the code always works as intended. It's a single page application, so when the user hits the back button to return to index.html and clicks again on one of the filter buttons, it indeed turns blue but the code inside of the .click() handler sometimes doesn't execute.

I suspect it might be because when index.html is reloaded, not all the click handlers get attached before the user clicks a filter button. But when the user clicks the same filter button again (total of twice), the handler usually executes.

Question Is there a way to have the code inside of click() wait to execute until all event handlers are attached first?

Instead of .click() I have also tried .on() but this has no effect. It might also be that the issue I'm identifying (event handlers attaching to elements too slowly) is wrong, but I would of course appreciate any feedback.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
orlando21
  • 307
  • 1
  • 5
  • 15
  • Of course, if you're adding them dynamically, not defining them in the HTML, that would be different. Specifically, it [would be this](http://stackoverflow.com/questions/203198/event-binding-on-dynamically-created-elements). – T.J. Crowder Nov 11 '17 at 14:28
  • @T.J.Crowder thanks for your feedback. If by "adding them dynamically" you mean the elements themselves, no the HTML filter buttons are all static. Are you saying the `ready` callback generally ensures that all event handlers will be attached to their elements? – orlando21 Nov 11 '17 at 14:40
  • All that `ready` guarantees is that the callback you give it will be called when the DOM is fully parsed. I think I know what's happening in your scenario; see my updated answer. :-) – T.J. Crowder Nov 11 '17 at 14:49

1 Answers1

2

The only explanation I can see for the described behavior is that the HTML is so big that it takes a while for the browser to finish parsing it and creating the DOM. While it's doing that, it will partially render the page. By using ready, you're telling jQuery you don't want the code in your callback to be called until that process is complete. That leaves a (small) window of opportunity for a user to see a button and click it before the click handler is attached (because the DOM parsing is still happening, and so the ready callback hasn't been fired yet). This fits the described symptoms, in particular that the first click doesn't work (ready hasn't fired yet) but a subsequent one does (ready fired in the meantime).

If that's what's happening, you can avoid it by using event delegation and not waiting for the ready callback:

$(document).on("click", ".filter_button_class", function() {
    // ...handle the click...
});

That hooks a click handler on document that will only get fired if the click passed through a .filter_button_class element. If it did, your handler is called as though you had hooked the event on that element instead.

This involves breaking one of the rules of scripts, which is to put them at the end of the HTML, just before the closing </body> tag. Instead, specifically because we're dealing with a problem caused by the size of the HTML and the DOM build time, we'd break that rule and put the script tags above the problematic content:

<script src="jquery.js"></script>
<script src="yourcode.js"></script>
<!-- problematic (large) content -->

Again, normally we wouldn't do that because it makes the DOM parser wait for the scripts to be fetched, but in this case, as a response to a known issue, it's fine. (Just ensure caching is enabled and working.)

You can see the effect by comparing this fiddle (using ready) with this fiddle (using a delegated up-front handler and then also a direct handler added via ready). With the first, if you're quick, you can click a button and have it not show anything. With the second, if you're quick, you can click a button and only get the "delegated:" message, but later if you click you'll get a "direct:" message and then a "delegate:" one.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I think your suggestions are spot on as I see no evidence to the contrary. I'm still working through your solution but wanted to ask about what you mean by caching. Do you mean simply placing the JavaScript in separate files - to load once and then they're still in memory, or do you have in mind something else? Thanks! – orlando21 Nov 12 '17 at 08:46
  • @orlando21: I mean ensure that (for instance) you're loading jQuery from a source that sets far-future cache headers so that the browser can keep it in cache. (Same with your own script as well, if it's in a separate file.) – T.J. Crowder Nov 12 '17 at 08:54