Search code examples
jqueryperformancecoffeescriptturbolinks

Turbolinks 5, jQuery 3: anyway to avoid event.off(...).on(...) for duplicate events?


I'm building a Rails 4 application with Turbolinks 5 and jQuery 3.

On my main global.coffee where I do my app loading, I use a CoffeeScript class to abstract some of the event handling:

# app/javascripts/global.coffee
$(document).on "turbolinks:load", ->
  App.MD.ExpansionPanel.init()

# app/javascripts/expansion_panel.coffee
class App.MD.ExpansionPanel
  @init: () ->
    $(document).off("click", "[data-behavior='expansion-panel-toggle']").on "click", "[data-behavior='expansion-panel-toggle']", (e) ->
      $panel = $(this).closest("[data-behavior='expansion-panel']")
      $details = $panel.find("[data-behavior='expansion-panel-details']")

      if $panel.attr("data-state") == "expanded"
        $panel.attr("data-state", "collapsed")
      else
        $panel.attr("data-state", "expanded")

# Some sample HTML (clicking any toggle element toggles between the summary or detail view)
<div data-behavior="expansion-panel">
  <div data-behavior="expansion-panel-summary">
    <div data-behavior="expansion-panel-toggle">Summary Toggle</div>
  </div>
  <div data-behavior="expansion-panel-details">
    <div data-behavior="expansion-panel-toggle">Details Toggle</div>
  </div>
</div>

The problem is, that the events were firing multiple times and my expand/collapse panels would toggle both events, thus returning the panel to the original state.

I fixed this by using .off("click", ...).on("click", ...) to first remove the click event and then re-add it, but something tells me this isn't very performant or a great solution, since I've never had to do this before.

Is there a more performant way to setup this event handler?

I have dynamic content added via AJAX, which is why I added the event to the document itself and not some parent context.


Solution

  • It seems that $(document).on('click','.selector', function(){...}) should be declared outside of $(document).on('turbolinks:load', function(event) {..});.

    This lead me to the solution: https://www.rubydoc.info/gems/jquery-turbolinks/2.1.0#Events_firing_twice_or_more

    And the current docs say something similar:

    When possible, avoid using the turbolinks:load event to add other event listeners directly to elements on the page body. Instead, consider using event delegation to register event listeners once on document or window. -- https://github.com/turbolinks/turbolinks#observing-navigation-events

    I had a similar case (an off-canvas menu), that only worked with unbinding (*.off()) the event first. Now with binding the event outside of turbolinks:load, it works as expected.