I have a mix of dynamically created inputs and normally defined inputs. The dynamically created inputs behave like a large vertically stacked block that the other inputs flow around. How do I make them all flow together?
This reproduces the issue:
library(shinydashboard)
shinyApp(
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
flowLayout(
uiOutput('input_fields'),
textInput('fielda','Field A',''),
textInput('fieldb','Field B',''),
textInput('fieldc','Field C',''),
textInput('fieldd','Field D','')
)
)
),
server = function(input, output) {
output$input_fields <- renderUI({
lapply(1:4, function(i) {
textInput(paste0('field',i), paste('Field',i),'')
})
})
}
)
Current layout:
Desired layout:
Edit: Ignore the Field A,B,C,D widgets. They are just for showing how other items wrap but the uiOutput ones act as one block. Assume all inputs are dynamic.
I think I'm to use this code instead. It moves the renderUI inside the lapply to create multiple generic outputs (varies at runtime, up to 10) which I can reference with equally generic uiOutput statements. It doesn't even warn about the 5 outputs that don't exist. Not perfect but it'll do. Probably need to add in an observeEvent as I don't think it will be reactive on its own.
library(shinydashboard)
shinyApp(
ui = dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
wellPanel(
flowLayout(
uiOutput('field1'),
uiOutput('field2'),
uiOutput('field3'),
uiOutput('field4'),
uiOutput('field5'),
uiOutput('field6'),
uiOutput('field7'),
uiOutput('field8'),
uiOutput('field9'),
uiOutput('field10')
)
)
)
),
server = function(input, output) {
lapply(1:5, function(i) {
output[[paste0('field',i)]] <- renderUI({
textInput(paste0('field',i), paste('Field',i),'')
})
})
}
)
All four dynamic generated widgets are treated as one object because they are contained in a list. Therefore it is quite difficult to position them. The easiest solution I can think of is to use
insertUI
function from the newest shiny version which can be installed usingdevtools
package:(Here you can read/learn more about
insertUI
andremoveUI
)We have to tell
insertUI
where it should add new widgets to the user interface, and we do it by specifying the parameterselector
(it has to be a string that is accepted by jQuery selector) and the parameterwhere
(there are four choices which which specify how widgets should go relative to the selector)So if we want to add a new widget under
Field 1
we have to setselector = "#fielda"
(We use#
selector since we are referring to the ID). Similarly, we add another widget underField 2
by settingselector = "#fieldb"
and so on. Following your naming convention we can generalize it to:In all cases we also set
where = "afterEnd
. It will position new widgets below reference widgets. (There is no space to position them above reference widgets)Now we can wrap
insertUI
into afor-loop
Note that we don't need to add any
uiOutput
on the client side.The other solution would be to render whole
dashboardBody
on the server side creating dynamically distinctuiOutput
s...which wouldn't be pleasant.When we take a look at the picture we can see that I did it the other way around - capital letters in names in the first row and numbers in names in the second row. It shouldn't be a big deal I hope :)
Full example:
EDIT:
If there are no reference widgets then you can also dynamically define
flowLayout
on the server side using non-standard evaluation.Say, you want to generate three
textInput
widgets. You would normally do this by typingon the client side. You can do the same thing by pasting strings with
paste0
function on the server side,saving it in a variable, say,
UI
, then parsing string and finally evaluating it.In the example below you have 15 dynamically generated widgets
Full example: