Adding a HTML editor to Shiny

Hello,

I recently had to implement a form in shiny that required an HTML editor inside. I quickly ran into a handy javascript library called tinyMCE. As described on their website,

TinyMCE enables you to convert HTML textarea fields or other HTML elements to editor instances.

tinyMCE is the same HTML editor that you can find on popular website, such as WordPress, Evernote, LinkedIn, etc.

I looked into the documentation and examples, then implemented it in the shiny app. Here is how:

First of all, the UI is as follows (explanation below).

library(shiny)
library(shinyjs)


shinyUI(
 fluidPage(
 tags$head(
 useShinyjs(),
 tags$script(src='https://cdn.tinymce.com/4/tinymce.min.js')
 ),

 fluidRow(
  titlePanel("tinyMCE Shiny"),
  br(),
  column(width=6,
   tags$form(method="post",
    tags$textarea(id="textHolder")
   ),
   br(),
   actionButton("fetch", "Get Results!", icon=icon("lightbulb-o"),class="btn-primary",
  onclick = "Shiny.onInputChange('typedText', tinyMCE.get('textHolder').getContent());"),
   tags$script("tinymce.init({
              selector:'#textHolder',
              theme: 'modern',
              height: 500,
              plugins: ['advlist autolink link image lists charmap preview hr','wordcount',],
              menubar: true,
              toolbar: 'undo redo | bold italic | bullist | link',
              });")
  ),
  column(width=6,
   tags$style(HTML('pre {height:240px;}')),
   tags$label(`for`="rawText", "Raw String"),
   hr(),
   tags$pre(textOutput("rawText")),
   br(),
   tags$label(`for`="htmlText", "HTML Version"),
   hr(),
   tags$pre(htmlOutput("htmlText"))
  )
 )
 )
)

Sorry for the horrible formating, it seems that WordPress (and tinyMCE!) doesn’t like copy/paste! I read about a plugin that is supposed to solve that issue, I should look into it.

So it’s a pretty simple app. In the head, I load the tinyMCE script from the direct URL for reproducibility but it’s better practice to download the script and put it in your www subfolder in your shiny app, in order to avoid version issue. In the first column, I included a textarea, then the tinyMCE JS function that “activate” the HTML editor. What happens behind the scene is that the textarea is hidden, and the HTMl editor is “iframed” into the app at the same spot.

I added a button that fetches the HTML string from the editor using  “Shiny.onInputChange(‘typedText’, tinyMCE.get(‘textHolder’).getContent());”.  The results can after be accessible through input$typedText, and are then displayed in the 2 boxes on the other column, one is the raw text string, the other is the HTML version of the code. And magic, they match with the editor (it’s a beautiful world, right?).

Another good practice is to put your JS code in a separate file (the onclick event and the tinyMCE “activator”), and then load it at the end of the page with “includeScript()” or “tags$script”.

The server part is very simple, it is just used to fill the boxes on the second column:

shinyServer(function(input, output, session) {

output$htmlText <- renderUI({
req(input$typedText)
HTML(enc2utf8(input$typedText))
})

output$rawText <- renderText({
req(input$typedText)
enc2utf8(input$typedText)
})
})

And that’s it! Pretty straightforward.

You may also want to include the HTML editor as part of a form that the user would fill (in a modal, usually). Modals are not yet included into Shiny, but they should be in version 0.14 (source: RStudio).

I wrote below a quick app to do so. When you click the button, a modal will pop up with the HTML Editor, and when you close it, the HTML string will be typed in the box below the button.

library(shiny)
library(shinyjs)

ui <- shinyUI(
fluidPage(
tags$head(
useShinyjs(),
tags$script(src=’https://cdn.tinymce.com/4/tinymce.min.js&#8217;)
),

# Application title
fluidRow(
titlePanel(“tinyMCE Modal Example”),
br(),
actionButton(“modal”, “Modal Example”, icon=icon(“paper-plane-o”), class=”btn-                               success”, style=”margin-left:15px;”,
`data-toggle`=”modal”, `data-target`=”#modalExample”),
br(),
br(),
tags$pre(htmlOutput(“modalText”)),
### Modal ###
tags$div(class=”modal fade”, id=”modalExample”, tabindex=”-1″, role=”dialog”,              `aria-labelledby`=”modalExample”, `aria-hidden`=”true”,
tags$div(class=”modal-dialog”, role=”document”,
tags$div(class=”modal-content”,
tags$div(class=”modal-header”,
tags$button(type=”button”, class=”close”, `data-dismiss`=”modal”, `aria-                     label`=”Close”,
tags$span(`aria-hidden`=”true”, ‘x’)),
tags$h4(class=”modal-title”, ‘HTML Editor in a modal’)
),
tags$div(class=”modal-body”,
tags$form(method=”post”,
tags$textarea(id=”modalEditor”)
),
tags$script(“tinymce.init({
selector:’#modalEditor’,
theme: ‘modern’,
height: 200,
});”)
),
tags$div(class=”modal-footer”,
tags$button(type=”button”, class=”btn btn-primary”, `data-dismiss`=”modal”,
onclick=”Shiny.onInputChange(‘modalTextArea’,                                                                      tinyMCE.get(‘modalEditor’).getContent());”,
‘Close’))
)
)
)
### End Modal ###
)
)
)

server <- function(input, output, session) {
output$modalText <- renderUI({
req(input$modalTextArea)
HTML(enc2utf8(input$modalTextArea))
})
}

shinyApp(server=server, ui=ui)

Once again, it is good practice to put your JS in a separate file, and then load it.

All the options to customize your tinyMCE editor can be found in the official documentation.

Some thoughts: this would be a good widget to make. I tried to look into it, but I need to learn more about the htmlwidget package to be able to do so! Probably in the future!

If you have any questions/comments, don’t hesitate!

Rock On!

Advertisements