Since Rails 7 the turbo-rails library is included by default. Turbo Drive intercepts link clicks and form submits. It makes sure that only the <body>
part of the page is rerendered instead of the whole page.
This leads to TinyMCE not being properly detached and reattached when a Turbo Drive response is rendered. The textarea will appear without the TinyMCE editor. In this post we expand on the tinymce-rails
gem with a Stimulus controller to prevent this issue. The controller helps to reattach TinyMCE and respects the settings in config/tinymce.yml
.
If you want to follow along or check out the end result you can find an example respository here: https://github.com/david-uhlig/example-tinymce-rails7-turbo
1. Add the tinymce-rails
gem to your Gemfile
gem 'tinymce-rails'
Then run bundle install
2. Make the assets available
# /app/views/layouts/application.html.erb
<head>
...
<%= tinymce_assets %>
</head>
3. Create the tinymce.yml
config file
# /config/tinymce.yml
height: 500
menubar: false
toolbar:
- undo redo | blocks | bold italic | alignleft aligncenter alignright | bullist numlist outdent indent | removeformat | help
plugins:
- insertdatetime lists media table code help wordcount
4. Create the Stimulus controller
// /app/javascript/controllers/tinymce_controller.js
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ['input']
connect() {
let config = Object.assign({ target: this.inputTarget }, TinyMCERails.configuration.default )
tinymce.init(config)
}
disconnect () {
if (!this.preview) tinymce.remove()
}
get preview () {
return (
document.documentElement.hasAttribute('data-turbolinks-preview') ||
document.documentElement.hasAttribute('data-turbo-preview')
)
}
}
The preview()
method makes sure that Turbolinks is not in cache preview mode (Source)
5. Attach the TinyMCE editor to your textarea
# /app/views/example/index.html.erb
<div data-controller="tinymce">
<%= text_area_tag :body, "Hello, World", data: { tinymce_target: 'input' }, class: "tinymce", rows: 20, cols: 60 %>
</div>
<%= tinymce %>
We need to include the <%= tinymce %>
helper method to make sure the tinymce.yml
config is respected.
tinymce-rails
README for the general setup.- Blog post "Making TinyMCE work with Rails, Turbolinks and Stimulus" from djchadderton with the main idea for the Stimulus controller included in this demo.
The tinymce-rails
gem is not strictly necessary. You may also use the Tiny Cloud CDN similarly as described here.
0. Skip steps 1-3 and 5 from above
1. Add the following line to the <head>
section of app/views/layouts/application.html.erb
<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>
2. Add the initialization to your Stimulus controller
initialize() {
this.defaults = {
selector: '.tinymce',
toolbar: [
'styleselect | bold italic | undo redo',
'image | link'
],
plugins: 'lists link image table code help wordcount'
}
}
3. Replace the connect function in the Stimulus controller
connect() {
let config = Object.assign({ target: this.inputTarget }, this.defaults)
tinymce.init(config)
}
The settings will no longer be read from config/tinymce.yml
but from the Stimulus controller instead.
4. Attach the TinyMCE editor to your textarea
# /app/views/example/index.html.erb
<div data-controller="tinymce">
<%= text_area_tag :body, "Hello, World", data: { tinymce_target: 'input' }, class: "tinymce", rows: 20, cols: 60 %>
</div>
Note: we skip <%= tinymce %>
here.
I would be very interested in a solution utilizing bin/importmap pin tinymce
. This seems to be the favorable way going forward in Rails 7. I had no luck making it work, though.
Hope this helps someone! Happy to hear about improvements.
@secretpray Wow, I've been looking for this answer for a long time and I finally found it, thanks.