1

Using help from here, I'm trying to make a shiny app that looks like:

enter image description here You can add more such selectInput and textInput using the Add Operation button (and delete them using Remove Operation button). My code goes as:

library(shiny)

mylist <- c('boxPlot','logNorm','pca')
params <- list()
params[[1]] <- list('columns')
params[[2]] <- c()
params[[3]] <- c('column','nComp')

ui <- shinyUI(fluidPage(
    sidebarPanel(
        actionButton("add_btn", "Add Operation"),
        actionButton("rm_btn", "Remove Operation")
    ),
    mainPanel(uiOutput("textbox_ui"))
))

server <- shinyServer(function(input, output, session){
    # Track the number of input boxes to render
    counter <- reactiveValues(n = 0)
    #Track the number of input boxes previously
    prevcount <-reactiveValues(n = 0)

    observeEvent(input$add_btn, {
        counter$n <- counter$n + 1
        prevcount$n <- counter$n - 1
    })

    observeEvent(input$rm_btn, {
        if (counter$n > 0) {
            counter$n <- counter$n - 1 
            prevcount$n <- counter$n + 1
        }
    })

    textboxes <- reactive({
        n <- counter$n
        vals = c()
        indexOp = c()
        if (n > 0) {
            # If the no. of textboxes previously where more than zero, then 
            #save the text inputs in those text boxes 
            if(prevcount$n > 0){
                paramsVal <- list()
                if(prevcount$n > n){
                    lesscnt <- n
                    isInc <- FALSE
                }
                else{
                    lesscnt <- prevcount$n
                    isInc <- TRUE
                }
                for(i in 1:lesscnt){
                    inpid = paste0("textin",i)
                    vals[i] = input[[inpid]] 
                    for(j in 1:length(mylist)){
                        # if(strcmp(vals[i],mylist[j])){
                        if(vals[i]==mylist[j]){
                            indexOp[i] = j
                            break
                        }
                    }
                }
                if(isInc){
                    vals <- c(vals, mylist[1])
                    indexOp <- c(indexOp,1)
                }
                lapply(seq_len(n), function(i) {
                    tagList(
                        selectInput(
                            inputId = paste0("textin", i),
                            label = h3("Select operation"),
                            choices = mylist,
                            selected = vals[i]
                        ),
                        lapply(seq_len(length(params[[indexOp[i]]])), function(j) {
                            textInput(inputId = paste0("params",i,"_",j),label = params[[indexOp[i]]][j],value = "Parameter")
                        })
                    )                    
                })
            }
            else{    
                indexOp[1] = 1      
                lapply(seq_len(n), function(i) {
                    tagList(
                        selectInput(
                            inputId = paste0("textin", i),
                            label = h3("Select operation"),
                            choices = mylist,
                            selected = mylist[1]
                        ),
                        lapply(seq_len(length(params[[indexOp[i]]])), function(j) {
                            textInput(inputId = paste0("params",i,"_",j),label = params[[indexOp[i]]][j],value = "Parameter")
                        })
                    )
                })                
            }
        }
    })
    output$textbox_ui <- renderUI({ textboxes() })
})

shinyApp(ui, server)

mylist is the list of operations the user can choose from. params[[i]] contains the arguments that the i-th function in mylist would take. indexOp just helps me retrieve the arguments based on the user input. Would be more than happy to explain the code even more if needed, please let me know as a comment if that's the case.

The issue

What I expect from it is that:

  1. I click on add operation and get new fields for select and texts (function and argument).

  2. Default function should be boxPlot, and default textInput must be for columns.

  3. When I change the function from say boxPlot to pca, I should get 2 textInputs.

1 and 2 are happening as expected. But in 3, the textInput is not changed directly. It changes only after I add another operation after that. That is, if a operation is last, its textInputs don't change instantly but I have to add another operation to do so, but if it is not the last, they change instantly.

I want it work like the latter case, i.e., they change instantly irrespective of whether the operation is last or not. How can I do that? Thanks...

Ankit Kumar
  • 1,145
  • 9
  • 30

1 Answers1

0

and welcome in the wonderful and all-powerful world of Shiny ;)

I do not like giving a fully-written answer. But I recently did something similar. So here are some tips:
* I would recommend you to cut your code into pieces. I recently had some issues with a large Shiny module and cutting it into pieces (and writing abstract modules using (this)[https://www.r-bloggers.com/the-shiny-module-design-pattern/]) really helped.
* Also, check out for these functions: do.call(), switch() for different cases, formals() to check for a function argument (helpful to write the rest).

I might have better written this as a comment (let me know, I am not perfectly used to SO yet), but giving good practices seem to be more important than giving all-made answers.

Enjoy and let me know if I well understood your issue.

Elie Ker Arno
  • 346
  • 1
  • 11
  • Sorry but I don't think you understood my question. I don't want to call the function or anything like that. I am given a list that does contain some function, but I'm not calling any of those. I just want to map those functions to their arguments and let the user provide their value in a `textInput`. The issue I'm facing, as I wrote, is that the change in textInput doesn't happen instantly, it requires me to add another operation below it before the changes can be displayed – Ankit Kumar Jul 12 '19 at 09:58
  • 1
    Ok, I didn't get it at first. But I think I am still right ;) Indeed, I also tried something similar, and calling directly textInput didn't work, nor did it to write all in a single chunck. Prefer to have a function using `lapply()` and `do.call(textInput, ...)` in a module. I ensure you that following this will ease your task and the legibility of your work. – Elie Ker Arno Jul 12 '19 at 14:41
  • Thanks for the advice. I'll keep that it mind. For my case btw, I used `click(id)` to programatically trigger the add and remove operation buttons, and it did it. Not a proper solution, kind of just a work around :) – Ankit Kumar Jul 12 '19 at 15:21