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.
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.