3

I am trying to adapt this SO answer regarding how to auto-resize a textarea input via javascript for shiny R. Ideally, I want to avoid helper packages such as shinyJS.

First I tried a pure javascript implementation in which I load the javascript as is into the app (approach 1). Then I tried triggering the javascript function from an observeEvent within shiny (approach 2).

Both approaches are not working. It seems like I am missing something.

Approach 1:

library(shiny)

jsCode1 <- "
            var observe;
            if (window.attachEvent) {
            observe = function (element, event, handler) {
            element.attachEvent('on'+event, handler);
            };
            }
            else {
            observe = function (element, event, handler) {
            element.addEventListener(event, handler, false);
            };
            }
            function init () {
            var text = document.getElementById('text');
            function resize () {
            text.style.height = 'auto';
            text.style.height = text.scrollHeight+'px';
            }
            /* 0-timeout to get the already changed text */
            function delayedResize () {
            window.setTimeout(resize, 0);
            }
            observe(text, 'change',  resize);
            observe(text, 'cut',     delayedResize);
            observe(text, 'paste',   delayedResize);
            observe(text, 'drop',    delayedResize);
            observe(text, 'keydown', delayedResize);

            text.focus();
            text.select();
            resize();
            }

            init();
            "

shinyApp(ui = 

           fluidPage(

                        tags$script(jsCode1),

                        tags$head(

                          tags$style("
                                     textarea {
                                     border: 0 none white;
                                     overflow: hidden;
                                     padding: 0;
                                     outline: none;
                                     background-color: #D0D0D0;
                                     }
                                     "
                          )

                          ),

                               shiny::tagAppendAttributes(
                                 textAreaInput(inputId = "text",
                                               label = "Enter text here",
                                               placeholder = "insert your text here",
                                               width = "100%"),
                                 style = "width: 100%;")

                        ),

         server = function(input, output, session) {

         }
                      )

Approach 2:

library(shiny)

jsCode2 <- "

            Shiny.addCustomMessageHandler('handler1', init);

            function init (el) {
            var text = document.getElementById(el);
            function resize () {
            text.style.height = 'auto';
            text.style.height = text.scrollHeight+'px';
            }
            /* 0-timeout to get the already changed text */
            function delayedResize () {
            window.setTimeout(resize, 0);
            }
            observe(text, 'change',  resize);
            observe(text, 'cut',     delayedResize);
            observe(text, 'paste',   delayedResize);
            observe(text, 'drop',    delayedResize);
            observe(text, 'keydown', delayedResize);

            text.focus();
            text.select();
            resize();
            }"

shinyApp(ui = 

           fluidPage(

                        tags$script(jsCode2),

                        tags$head(

                          tags$style("
                                     textarea {
                                     border: 0 none white;
                                     overflow: hidden;
                                     padding: 0;
                                     outline: none;
                                     background-color: #D0D0D0;
                                     }
                                     "
                          )

                          ),

                               shiny::tagAppendAttributes(
                                 textAreaInput(inputId = "text",
                                               label = "Enter text here",
                                               placeholder = "insert your text here",
                                               width = "100%"),
                                 style = "width: 100%;")

                        ),

         server = function(input, output, session) {

           observeEvent(input$text,{

             session$sendCustomMessage("handler1", message = "text")

           })

         }
                      )
TimTeaFan
  • 17,549
  • 4
  • 18
  • 39

1 Answers1

5

It could be the case that you tried to attach the resizing event to the textinput() before the latter was added to the DOM.

I added an event listener that waits until the DOM is loaded before the resizing event is attached.

document.addEventListener('DOMContentLoaded', function(event) {...})

shinyjs will do that automatically for you. Adding the event listener above, you can make it work without using shinyjs.

Javascript code:

jsCode <- "document.addEventListener('DOMContentLoaded', function(event) {
    var observe;
    if (window.attachEvent) {
      observe = function (element, event, handler) {
        element.attachEvent('on'+event, handler);
      };
    }
    else {
      observe = function (element, event, handler) {
        element.addEventListener(event, handler, false);
      };
    }
    function init () {
      var text = document.getElementById('text');
      function resize () {
        text.style.height = 'auto';
        text.style.height = text.scrollHeight+'px';
      }
      /* 0-timeout to get the already changed text */
        function delayedResize () {
          window.setTimeout(resize, 0);
        }
      observe(text, 'change',  resize);
      observe(text, 'cut',     delayedResize);
      observe(text, 'paste',   delayedResize);
      observe(text, 'drop',    delayedResize);
      observe(text, 'keydown', delayedResize);

      text.focus();
      text.select();
      resize();
    };init()
  })
"

The app:

library(shiny)

ui <- fluidPage(
  shiny::tags$script(jsCode),
  textAreaInput(inputId = "text", label = "a", value = "b")
)

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

shinyApp(ui, server)
Tonio Liebrand
  • 17,189
  • 4
  • 39
  • 59
  • Thanks +1, but do you have any idea why this does not work anymore when the `textAreaInput` is generated on the server side with `renderUI` ? Does doing this modify the ID of the output which cannot simply be accessed as `text` by js ? – Antoine May 06 '23 at 16:42
  • 1
    thats probably because the javascript code is run before the input is generated. You could try to make use of the onFlushed function: https://shiny.rstudio.com/reference/shiny/latest/onflush. I did not try, but something along "session$onFlushed(shiny::tags$script(jsCode),....). If that doesnt work, feel free to add a question and link it here. – Tonio Liebrand May 08 '23 at 11:50
  • Thanks for getting back to me. Will try what you suggest and post here if I have to create a new question. – Antoine May 10 '23 at 15:23