tinymce-rails icon indicating copy to clipboard operation
tinymce-rails copied to clipboard

Turbo + TinyMCE — doesn't reinit the editor after a Turbo interaction

Open jasonfb opened this issue 2 years ago • 9 comments

Symptom: Although TinyMCE inits once after a page load, if you make a Tubro interaction (either a whole page render or a frame replacement), the TinyMCE widget does not reinitialize to the textarea after your first Turbo interaction.
tinymce-with-turbo2

Note: there are various suggestions on setting up TinyMCE, but I have the most basic in my <head>

<head>
<%= tinymce_assets %>

    <script>
      TinyMCERails.configuration.default = {
        selector: "textarea.tinymce",
        cache_suffix: "?v=6.7.0",
        menubar: "insert view format table tools",
        toolbar: ["bold italic | link | undo redo | forecolor backcolor | bullist numlist outdent indent | table | uploadimage | code"],
        plugins: "table,fullscreen,image,code,searchreplace,wordcount,visualblocks,visualchars,link,charmap,directionality,nonbreaking,media,advlist,autolink,lists",
        images_upload_url: "/uploader/image"
      };
    </script>
<!-- more head content -->
</head>

Then you of course attach class tinymce to your textareas. This works for the 1st pickup, but after navigating with Turbo interactions, TinyMCE stops working.

jasonfb avatar Oct 07 '23 19:10 jasonfb

Ok, what's going on here is a little tricky, important to grok before moving forward: • TinyMCE doesn't seem to like to be re-inited after it exists in memory, therefore, it must be unloaded before the turbo interaction and reloaded after the turbo interaction.

To do this, here's what I've done

I am using a JSBundling app with the Sprockets pipeline, so my app/application/javascript.js contains this:

import "./tinymce_init"

Then I have a file app/application/tinymce_init.js with this content:

const reinitTiny = () => {
  console.log("connecting tiny...")
  console.log(tinymce);


  tinymce.init({
    selector: 'textarea.tinymce', // Add the appropriate selector for your textareas
    // Other TinyMCE configuration options
  });
}

window.addEventListener('turbo:before-fetch-response', () => {
  tinymce.remove();
  tinymce.init({selector:'textarea.tinymce'});
})
window.addEventListener('turbo:frame-render', reinitTiny)
window.addEventListener('turbo:render', reinitTiny)

(As this is all global code, you can easily put it into the <head> of your DOM, but I have done it this way just to keep the head clean)

Please note that the above code works with page re-renders and frame renders, I have not tested stream renders.

After this fix, TinyMCE re-initializes appropriately between Turbo interactions

tinymce-with-turbo4

jasonfb avatar Oct 07 '23 19:10 jasonfb

As a sidenote: with Turbo, you really need to put the Javascript in the head. Putting it in the body gives strange problems (where Turbo inserts the scripts tags on navigation).

wvengen avatar Nov 03 '23 11:11 wvengen

I just took the initialization code and put it into a stimulus controller connect() method, and added the tinymce.remove(); at the beginning. This forces a reload whenever the stimulus controller is loaded, only in pages that use it.

dgm avatar Jun 21 '24 15:06 dgm

I just took the initialization code and put it into a stimulus controller connect() method, and added the tinymce.remove(); at the beginning. This forces a reload whenever the stimulus controller is loaded, only in pages that use it.

I am doing something like the below. Seems to work, but there is some 2-3 sec loading delay once in a while when navigate away from the page that has editor. Still trying to find a better way.

export default class extends Controller {
    connect() {
        tinymce.init({...});
    }
    disconnect() {
        tinymce.remove();
    }
}

yjchieng avatar Jun 21 '24 18:06 yjchieng