0

I am trying to create a Shiny App which can be used in the R workspace to create a user friendly front end to some code- so that someone can just type in and click some boxes instead of creating lists and dataframes themselves- and then what they input will be stored in the workspace in R to do the code. I have basically adapted someone else's code but can't work out how I save the dynamically created UI called col - which makes text inputs so if people type something in this is saved.

When I try to add some way of saving it I get an error Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.). The code is below, is there a way I can save the information from the text input?

CrossBreakUI <- function(id, number) {

  ns <- NS(id)

  tagList(
    fluidRow(
      column(4,   numericInput(ns("n"), "Number of Groups in Cross-Break", value=5, min=1),     uiOutput(ns("col")))

    )
  )


}


variables <- function(input, output, session, variable.number){
  output$textInput <- renderUI({
    ns <- session$ns
    textInput(ns("textInput"),
              label = "")
  })


  col_names <- reactive(paste0("col", seq_len(input$n)))
  output$col <- renderUI({
    ns <- session$ns
    map(col_names(), ~ textInput(ns(.x), NULL))
  })


  reactive({



    # Create Pair: variable and its value
    df <- data.frame(
      "variable.number" = variable.number,

      stringsAsFactors = FALSE
    )



  })



}

ui <- fixedPage(

  div(
    CrossBreakUI("var1", 1)



  ))


server <- function(input, output) {




  add.variable <- reactiveValues()
  add.variable$df <- data.frame(
    "n" = numeric(0),
    "col" = character(0),
    stringsAsFactors = FALSE

  )


   var1 <- callModule(variables, paste0("var", 1), 1)



  observeEvent(input[[NS(paste0("var", 1), "n")]], {
    add.variable$df[1,1] <- input[[NS(paste0("var", 1), "n")]]
  })
**#THIS IS THE ERROR- IT DOES NOT SAVE THE TEXT INPUT FROM THIS VARIABLE**  
  observeEvent(input[[NS(paste0("var", 1), "col")]], {
    add.variable$df[1,2] <- input[[NS(paste0("var", 1), "col")]]
  })


observe({
    assign(x ="CrossBreak", value=add.variable$df, envir= .GlobalEnv) })

}
  • Welcome to SO! Your chances of getting an answer will greatly increase if you provide a simple, self-contained example of your problem. [This post](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example/5963610#5963610) may be helpful. Your code isn't really simple: there's an awful lot that doesn't relate to your problem! And which line is the one causing the error? You need to give more context so that we have the best chance of helping you effectively. – Limey Jun 12 '20 at 10:15
  • Hi, have noted your feedback and hopefully have made what I am asking a lot clearer!! – Ruth Newton Jun 12 '20 at 16:39

1 Answers1

0

Second revision

If my understanding is correct, I think this gets close to what you want. You have a numericInput. The UI presents a series of textInputs. The number of textInputs changes in response to changes in the numericInput's value. The values of the textInputs are saved to a variable in the global environment (and the value of this global variable is printed to the console as the app terminates). Values already entered in the textInputs are preserved when the UI updates.

My solution differs from yours in that you have one module attempting to control every textInput and a main server that attempts to interrogate each textInput to obtain its value. I use multiple instances of a single module, one for each textInput. Each module instance manages the persistence of its textInput's value independently of all the other instances.

library(shiny)

groupList <- list()

# Module to define a self-contained "write-my-value" textInput 
writeMyValueTextInputUI <- function(id, idx) {
  ns <- NS(id)

  textInput(ns("groupName"), paste0("Group ", idx))
}

writeMyValueTextInput <- function(input, output, session, id) {
  ns <- session$ns

  # Initialise
  observe ({
    id <- as.numeric(id)
    if (id <= length(groupList)) updateTextInput(session, "groupName", value=groupList[[id]])
  })

  observeEvent(input$groupName, {
    req(input$groupName)
    # Note global assignment
    groupList[[id]] <<- input$groupName
  })

  rv <- reactive ({
    input$groupName
  })

  return(rv)
}

ui <- fluidPage(
   titlePanel("Crossbreak demo"),
   sidebarLayout(
      sidebarPanel(
        numericInput("groupCount", "Number of groups in cross-break:", min=1, value=5),
      ),
      mainPanel(
        textOutput("groupCount"),
        uiOutput("groupList")
      )
   )
)

server <- function(input, output, session) {
  onStop(function() cat(paste0(groupList, collapse=", ")))
  ns <- session$ns

  controllers <- list()

  output$groupList <- renderUI({
    req(input$groupCount)
    textInputs <- lapply(
                    1:input$groupCount,
                    function(x) {
                      id <- ns(paste0("text", x))
                      controllers[[x]] <- callModule(writeMyValueTextInput, id, x)
                      return(writeMyValueTextInputUI(id, x))
                     }
                  )
    do.call(tagList, textInputs)
  })
}

shinyApp(ui = ui, server = server)

=========================
I haven't tried running your code (it's not really a simple self-contained example), but the following is just one way of running an app from the console. (is that what you mean when you say "from the global environment?)...

myList <- list(
  ui = bootstrapPage(
    numericInput('n', 'Number of obs', 100),
    plotOutput('plot')
  ),
  server = function(input, output) {
    output$plot <- renderPlot({ hist(runif(input$n)) })
  }
)

if (interactive()) runApp(myList)

I based my code on this page which also has other examples...

Note that you can't do this if you're running an R script in a batch job, as the batch job has no context in which to display the app. Hence if (interactive())...

OK. Responding to OP's clarification, here's a really crude demonstraion of one way of doing what she wants. Note the use of the global assignment operator (<<-) in the observeEvent.

x <- NA
print(paste0("globalValue is currently: ", x))

myList <- list(
  ui = bootstrapPage(
    numericInput('n', 'Please give me a number', 100)
  ),
  server = function(input, output) {
    observeEvent(input$n, {x <<- input$n})
  }
)

if (interactive()) runApp(myList)
print(paste0("globalValue is now: ", x))

On my system, stepping through these statements in the console gives:

> x <- NA
> print(paste0("globalValue is currently: ", x))
[1] "globalValue is currently: NA"
> myList <- list(
+   ui = bootstrapPage(
+     numericInput('n', 'Please give me a number', 100)
+   ),
+   server = function(input, output) {
+     observeEvent(input$n, {x <<- input$n})
+   }
+ )
> if (interactive()) runApp(myList)

Listening on http://127.0.0.1:4429

> print(paste0("globalValue is now: ", x))
[1] "globalValue is now: 104"
> 

Obviously, this isn't a realistic production solution. Possible solutions might include:

  • Writing to a temporary Rds file in the app and then reading it in once the app terminates.
  • Using session$userData to store the required information whilst the app is running and then using onStop to do custom processing as the app terminates.

I'm sure there will be others.

[OP: As an aside, look at the length of my code compared to yours. Put yourself in the shoes of someone who's willing to provide solutions. Whose question are they most likely to answer: yours or mine? Providing compact, relevant code makes it far more likely you'll get a useful reply.]

Limey
  • 10,234
  • 2
  • 12
  • 32
  • Sorry that my question wasn't very clear. I meant more how do I save answers which have been typed in for a dynamic UI so that they are in the R workspace. Ie creating a list of the typed in answers. It works saving standard inputs but I can't save the dynamic UI which has x textboxes based on what number someone types. Thank you for looking at this- I appreciate your help! – Ruth Newton Jun 12 '20 at 16:22
  • Still confused, but I think I;m beginning to understand. You have a module with a `numericInput` (called `.n`). You create an array `textInput`s and, ultimately, the intent is to store the values of these `textInput`s in a way that enables then to be available"outside" the app. Am I close? If so, there's definitely a cleaner way to do things. – Limey Jun 13 '20 at 13:14
  • Hi Limey, that is exactly what I want to be able to do ! – Ruth Newton Jun 15 '20 at 08:08
  • @Limey I think here in the example it's not relevant, but in general you should use `controllers[[x]] <<- callModule(writeMyValueTextInput, id, x)` because otherwise the return values of the modules only exist in the `lapply` environment – starja Jul 16 '20 at 18:02
  • @starja. I take your point, but I believe you've missed the `contollers <- list()` line earlier in the `server` function. That scopes `controllers` to the server, which is appropriate (and important in more complicated examples). But I agree that the difference is not critical in this simple case. – Limey Jul 17 '20 at 06:39