Ed_ Ed_ - 16 days ago 11
jQuery Question

Turbolinks duplicating rich text editor

I'm using Rails 5 with turbolinks and summernote which is a simple WYSIWYG Editor. I'm currently loading the plugin on turbolinks:load, like this:

$(document).on('turbolinks:load', function() {
$('[data-provider="summernote"]').each(function(){
$(this).summernote({ });
})
}


But if I navigate somewhere else and then press the back button on my browser the editor gets duplicated

enter image description here

I tried with jquery document ready, but then the plugin doesn't get loaded unless the page I load first is the one with the editor. Please help.

Answer

I know there are some changes to Turbolinks between Rails 4.2 and 5, but here is my take on this.

In many cases, you can simply adjust your code to listen for the turbolinks:load event, which fires once on the initial page load and again after every Turbolinks visit.

https://github.com/turbolinks/turbolinks#running-javascript-when-a-page-loads

If the above quote is right, then each time you visit the page (whether on ready or page:load) the turbolink trigger will fire.

Now one misconception about Turbolinks is that visiting another page destroys the JavaScript elements within that page.

This is false, in fact this was the big issue with Turbolink in Rails 4.2.

You can test this out (don't have to take my word for it) by doing:

$(document).ready(...)

Then reload the page, navigate away, then come back.

If your code is still instantiated, then this proves that the code is persisted.

If this is the case, then we need to rethink how we are working with JS.

So the code persists and on means every time, but we want it to happen once whether the page is loaded by the browser or turbolinks.

I think the appropriate solution would be:

$(document).one('turbolink:load', ...)

EDIT

I was able to recreate the issue perfectly.

I think however that the behavior is... correct.

The issue is as follows:

  1. Summernote creates elems below your <div data-provide="summernote" then sets style="display:none;
  2. All the elements (including those added by summernote) are cached.
  3. Then when the code is re-executed (as it should unless its presentational like so) the code finds the <div data-provide="summernote"> just chilling above and recreates another summernote box.

Now comes a philosophical question that will change what solution applies:

Should we cache the page?

If the answer is yes, then the only option is to use the fact that summernote set's the div to be hidden to our advantage.

$(document).on('turbolinks:load', function() {
  $('[data-provider="summernote"]').each(function() {
    if ($(this).is(":visible"))
      $(this).summernote()
  })
})

Note that we could differentiate an instantiated summernote div differently (i.e. adding a class, or data attribute), but this is just a convenient way.

Otherwise, by not caching the page, everything is re-executed and all is good.

P.S. There will always be complications while using Turbolinks, not because it's a bad tool. But because JS libraries work off assumptions of how your page cycles work and Turbolinks is a complete overhaul of how page cycles work.

Hope this helps not only provide a solution but also some understanding!

Cheers

Comments