'Adding tinyMCE editor in Shiny modal

I'd like to create an HTML editor in a Shiny app, using the shinyMCE package.

This works well in the example below.

library(shiny)
library(shinyMCE)
library(shinyjs)
library(shinyWidgets)
library(shinydashboard)

ui <- dashboardPage(
  useShinyjs(),
  header = dashboardHeader(disable = T),
  sidebar = dashboardSidebar(disable = T),
  body = dashboardBody(
    tags$script(src = "http://cdn.tinymce.com/4/tinymce.min.js",
                referrerpolicy = "origin"),

    tinyMCE("editor", "The content"),
    actionButton("ok", "OK")
    ))

server <- function(input, output, session)
{
  observeEvent(
    input$ok,
    {
    print(input$editor)
    }
  )

  observeEvent(
    input$open,
    {
      showModal(myModal())
    })
}

shinyApp(ui, server = server)

Indeed, if you press OK, the content of the editor is printed in the R console.

Now, I'd like to put the editor in a modal. If I do the following the editor appears, but if I press OK the content doesn't get updated. That is, the R console always shows "the content", independently of what is written in the textarea.

library(shiny)
library(shinyMCE)
library(shinyjs)
library(shinyWidgets)
library(shinydashboard)

ui <- dashboardPage(
  useShinyjs(),
  header = dashboardHeader(disable = T),
  sidebar = dashboardSidebar(disable = T),
  body = dashboardBody(
    tags$script(src = "http://cdn.tinymce.com/4/tinymce.min.js",
                referrerpolicy = "origin"),

    flowLayout (
      actionButton("open", "Open")
      )))

myModal <- function()
  {
  modalDialog(size = "l",
              title = "A modal dialog",
              tinyMCE("tinyTxt", "the content"),
              actionButton("ok", "OK"),
              easyClose = T)
  }

server <- function(input, output, session)
  {
  observeEvent(
    input$ok,
      {
      print(input$tinyTxt)
      }
    )

observeEvent(
    input$open,
    {
    showModal(myModal())
    })
  }

shinyApp(ui, server = server)

In the JS console I get

Uncaught TypeError: Cannot read property 'getContent' of null
    at exports.InputBinding.getValue (<anonymous>:9:41)
    at c (init_shiny.js:117)
    at init_shiny.js:163
    at eN.<anonymous> (<anonymous>:16:18)
    at mp.c.fire (tinymce.min.js:2)
    at eN.fire (tinymce.min.js:2)
    at eN.<anonymous> (tinymce.min.js:2)
    at mp.c.fire (tinymce.min.js:2)
    at eN.fire (tinymce.min.js:2)
    at Rp (tinymce.min.js:2)

Any idea of how to get around the problem?

EDIT: One further observation. In the first (working) example tinyMCE.editors contains one instance of an editor, while in the second it is empty (although the editor does display!).



Solution 1:[1]

I managed to solve this, by manually creating the TinyMCE editor (which solves the issue of the editor not appearing in tinymce.editors) and then use some custom JS to retrieve the value. This seems a bit hacky to me, but it works... Here's an example

library(shiny)
library(shinyjs)
library(shinyWidgets)
library(shinydashboard)

ui <- dashboardPage(
  useShinyjs(),
  header = dashboardHeader(disable = T),
  sidebar = dashboardSidebar(disable = T),
  body = dashboardBody(
    singleton(tags$head(tags$script(src = "http://cdn.tinymce.com/4/tinymce.min.js",
                referrerpolicy = "origin"))),

    # Register a custom message handler that gets the content of the editor
    # and forces update of the textarea
    singleton(tags$head(tags$script("Shiny.addCustomMessageHandler('getTxt', 
        function(message) {
          var content = tinyMCE.get('tinyTxt').getContent();
          Shiny.onInputChange('tinyTxt', content);
          })"))),

    flowLayout (
      actionButton("open", "Open"),
      htmlOutput("content")
      )))

myModal <- function()
  {
  modalDialog(size = "l",
              title = "A modal dialog",
              textAreaInput("tinyTxt", "the content"),
              actionButton("ok", "OK"),
              easyClose = T)
  }

server <- function(input, output, session)
  {
  observeEvent(
    input$ok,
      {
      # Retrieve the content of the editor
      session$sendCustomMessage("getTxt", "")
      removeModal()
      })

  output$content <- renderText(       
    input$tinyTxt
    )


  observeEvent(
    input$open,
    {
    showModal(myModal())

    # Create the tinyMCE editor
    runjs("var ed = new tinymce.Editor('tinyTxt', {
        selector: 'tinyTxt',
        theme: 'modern'}, 
        tinymce.EditorManager);
        ed.render();")
    })
  }

shinyApp(ui, server = server)

Solution 2:[2]

For anyone looking at this in 2022, this is an updated solution working with version 6 of TinyMCE. You need to get an API key which is available on registration at https://www.tiny.cloud/

I also added a print to the R console with a delay() as the returned input was blank otherwise.

There are a few additional options compared to the original solution. I kept them there to show how it's done. Hopefully this is useful to someone!

library(shinyjs)
library(shinyWidgets)
library(shinydashboard)

ui <- dashboardPage(
  useShinyjs(),
  header = dashboardHeader(disable = T),
  sidebar = dashboardSidebar(disable = T),
  body = dashboardBody(
    tags$head(tags$script(src = "https://cdn.tiny.cloud/1/--API-KEY-HERE--/tinymce/6/tinymce.min.js",
                                    referrerpolicy = "origin")),

    # Register a custom message handler that gets the content of the editor
    # and forces update of the textarea
    tags$head(tags$script("Shiny.addCustomMessageHandler('getTxt', 
        function(message) {
          var content = tinymce.activeEditor.getContent();;
          Shiny.onInputChange('tinyTxt', content);
          })")),
    
    flowLayout (
      actionButton("open", "Open"),
      htmlOutput("content")
    )))

myModal <- function()
{
  modalDialog(size = "l",
              title = "A modal dialog",
              textAreaInput("tinyTxt", "the content"),
              actionButton("ok", "OK"),
              easyClose = T)
}

server <- function(input, output, session)
{

  
  
  observeEvent(
    input$open,
    {
      showModal(myModal())
      
      # Create the tinyMCE editor
      runjs("var ed = new tinymce.Editor('tinyTxt', {
        menubar: false,
branding: false,
plugins: 'lists, table, link',
contextmenu: 'lists, link, table',
toolbar1: 'bold italic forecolor backcolor | formatselect fontselect fontsizeselect | alignleft aligncenter alignright alignjustify',
toolbar2: 'undo redo removeformat bullist numlist table blockquote code superscript  subscript strikethrough link'}, 
        tinymce.EditorManager);
        ed.render();")
    

      })
  
  observeEvent(
    input$ok,
    {


      
      
      # Retrieve the content of the editor
      session$sendCustomMessage("getTxt", "")
      
      output$content <- renderText(       
        input$tinyTxt
      )
      
      delay(500, print(input$tinyTxt))
      
      removeModal()
    })

}

shinyApp(ui, server = server)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 nico
Solution 2 Gakku