Updating DataTable using DropDown Within DataTable - r

I am trying to update a sample DataTable dynamically using dropdowns that I created within the DataTable. However, it doesn't seem to update regardless of what I try. The sample code below is what I am currently working with in order to update the Species column when the input is selected from the species_selector column.
library(shiny)
library(DT)
ui <- fluidPage(
title = 'Selectinput column in a table',
h3("Source:", tags$a("Yihui Xie", href = "https://yihui.shinyapps.io/DT-radio/")),
DT::dataTableOutput('foo'),
actionButton(inputId = "submit", label = "Submit"),
verbatimTextOutput('sel')
)
server <- function(input, output, session) {
data <- head(iris, 5)
for (i in 1:nrow(data)) {
data$species_selector[i] <- as.character(selectInput(paste0("sel", i), "", choices = unique(iris$Species), width = "100px"))
}
output$foo = DT::renderDataTable(
data, escape = FALSE, selection = 'none', server = FALSE,
options = list(dom = 't', paging = FALSE, ordering = FALSE),
callback = JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-container');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());")
)
observeEvent(input$submit, {
update_data <- reactive({
df <- data
for(i in 1:nrow(df)) {
df$Species[i] <- as.character(input[[paste0("sel", i)]])
}
return(df)
})
data <- update_data()
})
output$sel = renderPrint({
for(i in 1:nrow(data)) {
data$Species[i] <- as.character(input[[paste0("sel", i)]])
}
data
})
}
shinyApp(ui, server)
Any help would be appreciated. Thank you!

Is it something like this you want?
library(shiny)
library(DT)
selector <- function(id, values, items = values){
options <- HTML(paste0(mapply(
function(i, item){
value <- values[i]
if(i == 1L){
opt <- tags$option(value = value, selected = "selected", item)
}else{
opt <- tags$option(value = value, item)
}
as.character(opt)
}, seq_along(values), items
), collapse = ""))
as.character(tags$select(id = id, options))
}
js <- c(
"function(settings) {",
" var table = this.api().table();",
" var $tbl = $(table.table().node());",
" var id = $tbl.closest('.datatables').attr('id');",
" var nrows = table.rows().count();",
" function selectize(i) {",
" var $slct = $('#slct' + i);",
" $slct.select2({",
" width: '100%',",
" closeOnSelect: true",
" });",
" $slct.on('change', function(e) {",
" var info = [{",
" row: i,",
" col: 4,",
" value: $slct.val()",
" }];",
" Shiny.setInputValue(id + '_cell_selection:DT.cellInfo', info);",
" });",
" }",
" for(var i = 1; i <= nrows; i++) {",
" selectize(i);",
" }",
"}"
)
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js")
),
br(),
DTOutput("dtable"),
tags$hr(),
h2("Edited table:"),
tableOutput("table")
)
server <- function(input, output, session) {
dat <- head(iris, 3L)
Dat <- reactiveVal(dat)
for(i in 1L:nrow(dat)){
dat$species_selector[i] <-
selector(id = paste0("slct", i), values = unique(iris$Species))
}
output[["dtable"]] <- renderDT({
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js),
preDrawCallback = JS(
"function() { Shiny.unbindAll(this.api().table().node()); }"
),
drawCallback = JS(
"function() { Shiny.bindAll(this.api().table().node()); }"
)
)
)
}, server = TRUE)
observeEvent(input[["dtable_cell_selection"]], {
info <- input[["dtable_cell_selection"]]
Dat(editData(Dat(), info, rownames = FALSE))
})
output[["table"]] <- renderTable({
Dat()
})
}
shinyApp(ui, server)

You can take help of reactive values to save the dataframe and perform the changes in the same.
library(shiny)
library(DT)
data <- head(iris, 5)
for (i in 1:nrow(data)) {
data$species_selector[i] <- as.character(selectInput(paste0("sel", i), "", choices = unique(iris$Species), width = "100px"))
}
ui <- fluidPage(
title = 'Selectinput column in a table',
h3("Source:", tags$a("Yihui Xie", href = "https://yihui.shinyapps.io/DT-radio/")),
DT::dataTableOutput('foo'),
actionButton(inputId = "submit", label = "Submit"),
verbatimTextOutput('sel')
)
server <- function(input, output, session) {
rv <- reactiveValues(data = data)
output$foo = DT::renderDataTable(
rv$data, escape = FALSE, selection = 'none', server = FALSE,
options = list(dom = 't', paging = FALSE, ordering = FALSE),
callback = JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-container');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());")
)
observeEvent(input$submit, {
for(i in 1:nrow(rv$data)) {
rv$data$Species[i] <- as.character(input[[paste0("sel", i)]])
}
})
}
shinyApp(ui, server)

Related

How to update row-wise filter in Shiny datatable

I'm trying to update row-wise filter in datatable basis on the inputs we receive from user on every row, so that only relevant values in sub-sequent inputs can be selected.
I have tried to replicate my scenario using below code, where in if User selects "setosa" as "spieces_selector" hence only "1-50" values should appear in "New_Data_selector". Similarly if a User selects "versicolor" in 2nd row hence for 2nd row "New_Data_selector" should have the values from "51-100".
Would appreciate your help on this.
library(shiny)
library(DT)
iris$New_Data <- c(1:150)
ui <- fluidPage(
title = 'Selectinput column in a table',
h3("Source:", tags$a("Yihui Xie", href = "https://yihui.shinyapps.io/DT-radio/")),
numericInput('num', "enter a number", value = 5, min = 1, max = 10, step = 1),
DT::dataTableOutput('foo'),
verbatimTextOutput('sel'),
actionButton(
"saveBtn",
"Submit Request",
style = "color: #fff; background-color: #282364;
border-color: #2e6da4",
class = "btn btn-primary"
)
)
server <- function(input, output, session) {
data <- reactive({
df <- head(iris, input$num)
for (i in 1:nrow(df)) {
df$species_selector[i] <- as.character(selectInput(paste0("sel1", i),
"",
choices = unique(iris$Species),
width = "100px"))
df$New_Data_selector[i] <- as.character(selectInput(paste0("sel2", i),
"",
choices = unique(iris$New_Data),
width = "100px"))
}
df
})
output$foo = DT::renderDataTable(
data(), escape = FALSE, selection = 'none', server = FALSE,
options = list(dom = 't', paging = FALSE, ordering = FALSE),
callback = JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-container');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());")
)
output$sel = renderPrint({
str(sapply(1:nrow(data()), function(i) input[[paste0("sel", i)]]))
})
observeEvent(input$saveBtn, {
Test_Data <- sapply(1:nrow(data()), function(i) input[[paste0("sel", i)]])
Test_Data <- as.data.frame(Test_Data)
print(Test_Data)})
}
shinyApp(ui, server)
The following works (based on my earlier answer) - but it's pretty slow. Will need to investigate further.
library(DT)
library(shiny)
library(datasets)
library(data.table)
myIris <- copy(iris)
setDT(myIris)
myIris[, Index := seq_len(.N)]
selectInputIDs_A <- paste0("sel_A", myIris$Index)
selectInputIDs_B <- paste0("sel_B", myIris$Index)
myIris[, selectInputs_A := sapply(selectInputIDs_A, function(x){as.character(selectInput(inputId = x, label = "", choices = unique(myIris$Species), selected = "setosa"))})]
myIris[, selectInputs_B := sapply(selectInputIDs_B, function(x){as.character(selectInput(inputId = x, label = "", choices = unique(myIris[Species == "setosa"]$Index), selected = "setosa"))})]
initTbl <- copy(myIris)
ui <- fluidPage(
DT::dataTableOutput(outputId = 'my_table')
)
server <- function(input, output, session) {
displayTbl <- reactive({
myIris[, selectInputs_A := sapply(selectInputIDs_A, function(x){as.character(selectInput(inputId = x, label = "", choices = unique(Species), selected = input[[x]]))}),]
myIris[, selectInputs_B := sapply(seq_along(selectInputs_B), function(x){as.character(selectInput(inputId = selectInputIDs_B[x], label = "", choices = unique(myIris[Species == input[[selectInputIDs_A[x]]]]$Index), selected = input[[selectInputIDs_A[x]]]))})]
})
output$my_table = DT::renderDataTable({
DT::datatable(
initTbl, escape = FALSE, selection = 'none', rownames = FALSE,
options = list(paging = FALSE, ordering = FALSE, scrollx = TRUE, dom = "t",
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
)
)
}, server = TRUE)
my_table_proxy <- dataTableProxy(outputId = "my_table", session = session)
observeEvent({sapply(selectInputIDs_A, function(x){input[[x]]})}, {
replaceData(proxy = my_table_proxy, data = displayTbl(), rownames = FALSE) # must repeat rownames = FALSE see ?replaceData and ?dataTableAjax
}, ignoreInit = TRUE)
}
shinyApp(ui = ui, server = server)

Choose a datatable row either by clicking, actionButton() or shiny widget

I have the shiny app below in which the user is able to choose a row to display its index with 3 ways.
Click on the row and display its index
Click on a row then press Next and display the index of the next row.
Select the rowname of a row,press Assign and display its index.
I know that I can use callback in order to enable datatable to achieve this but I do not know how to combine many callbacks.
library(shiny)
library(DT)
dat <- mtcars
callback <- JS(
"Shiny.addCustomMessageHandler(",
" 'selectRow',",
" function(index) {",
" table.row(index - 1).select();",
" }",
");",
"$('#btn-next').prop('disabled', true);",
"var selected_row = null;",
"table.on('select', function( e, dt, type, indexes ) {",
" $('#btn-next').prop('disabled', false);",
" selected_row = indexes[0];",
"});",
"table.on('deselect', function( e, dt, type, indexes ) {",
" $('#btn-next').prop('disabled', true);",
"});",
"var nrows = table.rows().count();",
"$('#btn-next').on('click', function() {",
" var next_row = selected_row + 1 < nrows ? selected_row + 1 : 0;",
" table.row(next_row).select();",
"});"
)
ui <- fluidPage(
br(),
DTOutput("dtable"),
br(),
textOutput("selectedRow"),
actionButton("btn-next", "select next row"),
pickerInput(
"rowname",
label = "Choose a row",
choices = setNames(1:nrow(dat), rownames(dat))
),
actionButton("assign", "Assign")
)
server <- function(input, output, session) {
output[["dtable"]] <- renderDT({
datatable(
dat,
extensions = "Select",
selection = "none",
callback = callback,
options = list(
columnDefs = list(
list(className = "dt-center", targets = "_all")
),
select = list(style = "single")
)
)
}, server = FALSE)
output[["selectedRow"]] <- renderText({
i <- input[["dtable_rows_selected"]]
paste0(
"Selected row: ",
ifelse(is.null(i), "none", i)
)
})
observeEvent(input[["rowname"]], {observeEvent(input[["assign"]], {
session$sendCustomMessage("selectRow", isolate(input[["rowname"]]))
})})
}
shinyApp(ui, server)
Full app:
library(shinydashboard)
library(shinydashboardPlus)
library(DT)
library(shinyjs)
library(shinyWidgets)
attribute_name <- c("Jack", "Bob", "Jack", "Bob")
category_id <- c(7, 7, 7, 7)
candidate_phrase_lemma <- c("apple", "olive", "banana", "tomato")
d <- data.frame(
attribute_name,
category_id,
candidate_phrase_lemma,
stringsAsFactors = FALSE
)
names <- tapply(d$candidate_phrase_lemma, d$attribute_name, I)
candidate_1 <- c("Jack", "Bob", "Jack", "Bob")
candidate_2 <- c("phone", "camera", "micro", "pc")
similarity <- c(4, 5, 6, 7)
category_id <- c(7, 7, 7, 7)
e <- data.frame(candidate_1, candidate_2, similarity, category_id)
selector <- function(id, values, items = values) {
options <- HTML(paste0(mapply(
function(value, item) {
as.character(tags$option(value = value, selected = "selected", item))
}, values, items
), collapse = ""))
as.character(
tags$select(
id = id, multiple = "multiple", options
)
)
}
dat <- data.frame(
attributes = unique(as.character(d$attribute_name)),
attributes_phrases = vapply(
1:length(names),
function(i) {
selector(paste0("slct", i), names[[i]])
},
character(1)
),
Count = lengths(names),
stringsAsFactors = FALSE
)
nrows <- nrow(dat)
initComplete <- c(
"function(settings) {",
" var table = this.api().table();",
" var nrows = table.rows().count();",
" function selectize(i) {",
" var $slct = $('#slct' + i);",
" $slct.select2({",
" width: '100%',",
" closeOnSelect: false",
" });",
" $slct.on('change', function(e) {",
" table.cell(i-1, 2).data($slct.val().length);",
" });",
" }",
" for(var i = 1; i <= nrows; i++) {",
" selectize(i);",
" }",
"}"
)
callback <- JS(
"Shiny.addCustomMessageHandler(",
" 'selectRow',",
" function(index) {",
" table.row(index - 1).select();",
" }",
");",
"$('#btn-next').prop('disabled', true);",
"var selected_row = null;",
"table.on('select', function( e, dt, type, indexes ) {",
" $('#btn-next').prop('disabled', false);",
" selected_row = indexes[0];",
"});",
"table.on('deselect', function( e, dt, type, indexes ) {",
" $('#btn-next').prop('disabled', true);",
"});",
"var nrows = table.rows().count();",
"$('#btn-next').on('click', function() {",
" var next_row = selected_row + 1 < nrows ? selected_row + 1 : 0;",
" table.row(next_row).select();",
"});"
)
js <- paste0(c(
"Shiny.addCustomMessageHandler(",
" 'addCandidate',",
" function(row_candidate) {",
" var i = row_candidate.row;",
" var candidate = row_candidate.candidate;",
" var $slct = $('#slct' + i);",
" if($slct.find(\"option[value='\" + candidate + \"']\").length === 0) {",
" var newOption = new Option(candidate, candidate, true, true);",
" $slct.append(newOption).trigger('change');",
" }",
" }",
");"
), collapse = "\n")
shinyApp(
ui = dashboardPagePlus(
tags$head(
tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"),
tags$style(HTML(
".select2-selection__choice {background-color: darkblue !important;}"
)),
tags$script(HTML(js))
),
useShinyjs(),
header = dashboardHeaderPlus(),
sidebar = dashboardSidebar(),
body = dashboardBody(
DTOutput("table"),
br(),
fluidRow(
column(
4,
uiOutput("ui-rowselect")
),
column(
2,
actionButton("selectrow", "Select this row")
)
),
br(),
actionButton("btn-next", "Select next row"),
br(), br(),
conditionalPanel(
condition = "input.table_rows_selected.length > 0",
wellPanel(
uiOutput("celltext"),
splitLayout(
actionButton("bc", "Previous candidate"),
actionButton("dec", "Next candidate"),
actionButton("addWord", "Add this candidate", class = "btn-info"),
cellWidths = "auto"
)
)
)
)
),
server = function(input, output, session) {
Text <- reactiveVal()
Data <- reactiveVal()
Candidate <- reactiveVal()
rnum <- reactiveVal()
output[["table"]] <- renderDT({
datatable(
data = dat,
extensions = "Select",
selection = "none",
escape = FALSE,
rownames = FALSE,
callback = callback,
options = list(
pageLength = 5,
columnDefs = list(
list(className = "dt-center", targets = "_all")
),
select = list(style = "single"),
initComplete = JS(initComplete),
preDrawCallback = JS(
"function() { Shiny.unbindAll(this.api().table().node()); }"
),
drawCallback = JS(
"function() { Shiny.bindAll(this.api().table().node()); }"
)
)
)
}, server = FALSE)
output[["ui-rowselect"]] <- renderUI({
selectedRow <- input[["table_rows_selected"]]
choices <- if(is.null(selectedRow)) 1:nrows else (1:nrows)[-selectedRow]
pickerInput(
"rowselect",
label = "Choose a row",
choices = choices
)
})
observeEvent(input[["selectrow"]], {
session$sendCustomMessage("selectRow", input[["rowselect"]])
})
observeEvent(input[["table_rows_selected"]], {
row <- input[["table_rows_selected"]]
dat <- e[e[["candidate_1"]] %in% d[row, 1], ]
Data(dat[order(dat[["similarity"]], decreasing = TRUE), ])
rnum(1)
})
output[["celltext"]] <- renderUI({
HTML(Text())
})
observeEvent(input[["dec"]], {
rnum(rnum() + 1)
})
observeEvent(input[["bc"]], {
rnum(rnum() - 1)
})
observeEvent(list(rnum(), Data()), {
if(rnum() == 1){
disable("bc")
}else{
enable("bc")
}
if(rnum() == nrows){
disable("dec")
}else{
enable("dec")
}
Candidate(Data()[rnum(), 2])
Text(
paste(
"Similarity of <em>", Data()[rnum(), 1], "</em>",
"to candidate <em>", Candidate(), "</em>",
"is <strong>", Data()[rnum(), 3], "</strong>"
)
)
}, ignoreInit = TRUE)
observeEvent(input[["addWord"]], {
session$sendCustomMessage(
"addCandidate",
list(row = input[["table_rows_selected"]], candidate = Candidate())
)
})
}
)

Display multiple strings in a cell of a datatable that can be removed by clicking on them

I have the shiny app below in which I convert the d dataframe to a dataframe in which the unique items will be summarized based on the name and a new column will be added with their count. Then I use DT package to display this dataframe. I wonder if DT or shinywidgets or maybe another method can be used in order to display the table like in the screenshot below in which the user will be able to display the different strings in the items column as separated words that he will be able to remove. Here is an example in the second column.
library(shiny)
library(DT)
library(jsonlite)
selector <- function(id, values, items = values){
options <- HTML(paste0(mapply(
function(value, item){
as.character(tags$option(value = value, item))
}, c("", values), c("", items)
), collapse = ""))
as.character(
tags$select(
id = id, class = "form-control", multiple = "multiple", options
)
)
}
name<-c("Jack","Bob","Jack","Bob")
item<-c("apple","olive","banana","tomato")
d<-data.frame(name,item)
words<-tapply(d$item, d$name, I)
nrows <- length(words)
js <- c(
"function(settings) {",
sprintf("var nrows = %d;", nrows),
sprintf("var words = %s;", toJSON(words)),
" var table = this.api().table();",
" function selectize(i) {",
" $('#slct' + i).selectize({",
" items: words[i-1],",
" onChange: function(value) {",
" table.cell(i-1, 2).data(value.length);",
" }",
" });",
" }",
" for(var i = 1; i <= nrows; i++) {",
" selectize(i);",
" Shiny.setInputValue('slct' + i, words[i-1]);",
" }",
"}"
)
ui <- fluidPage(
br(),
DTOutput("table"),
div( # this is a hidden selectize input whose role is to make
# available 'selectize.js'
style = "display: none;",
selectInput("id", "label", c("x", "y"))
)
)
server <- function(input, output, session) {
output[["table"]] <- renderDT({
dat <- data.frame(
FOO = c(unique(d$name)),
Words = vapply(
1:nrows,
function(i){
selector(paste0("slct", i), words[[i]])
},
character(1)
),
Count = lengths(words),
stringsAsFactors = FALSE
)
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js),
preDrawCallback = JS(
'function() { Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS(
'function() { Shiny.bindAll(this.api().table().node()); }'
)
)
)
}, server = FALSE)
}
shinyApp(ui, server)
We can do that with a selectizeInput:
library(shiny)
library(DT)
js <- c(
"function(settings){",
" $('#mselect').selectize();",
"}"
)
ui <- fluidPage(
br(),
DTOutput("table"),
div(
style = "display: none;",
selectInput("id", "label", c("x", "y"))
)
)
server <- function(input, output, session) {
output[["table"]] <- renderDT({
dat <- data.frame(
FOO = "bar",
BAZ = '<select id="mselect" class="form-control" multiple="multiple">
<option value=""></option>
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Lemon</option>
</select>',
stringsAsFactors = FALSE)
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js)
)
)
})
}
shinyApp(ui, server)
EDIT
library(shiny)
library(DT)
selector <- function(id, values, items = values){
options <- HTML(paste0(mapply(
function(value, item){
as.character(tags$option(value = value, item))
}, c("",values), c("",items)
), collapse = ""))
as.character(
tags$select(
id = id, class = "form-control", multiple = "multiple", options
)
)
}
words1 <- c("apple", "banana")
words2 <- c("olive", "tomato")
js <- c(
"function(settings) {",
sprintf("var words1 = [%s];", toString(shQuote(words1))),
sprintf("var words2 = [%s];", toString(shQuote(words2))),
" $('#slct1').selectize({items: words1});",
" $('#slct2').selectize({items: words2});",
" Shiny.setInputValue('slct1', words1);",
" Shiny.setInputValue('slct2', words2);",
"}"
)
ui <- fluidPage(
br(),
verbatimTextOutput("words1"),
DTOutput("table"),
div( # this is a hidden selectize input whose role is to make
# available 'selectize.js'
style = "display: none;",
selectInput("id", "label", c("x", "y"))
)
)
server <- function(input, output, session) {
output[["table"]] <- renderDT({
dat <- data.frame(
FOO = c("bar", "baz"),
Words = c(
selector("slct1", words1),
selector("slct2", words2)
),
stringsAsFactors = FALSE
)
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js),
preDrawCallback = JS(
'function() { Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS(
'function() { Shiny.bindAll(this.api().table().node()); }'
)
)
)
}, server = FALSE)
output[["words1"]] <- renderPrint({
input[["slct1"]]
})
}
shinyApp(ui, server)
EDIT
With the counts:
library(shiny)
library(DT)
selector <- function(id, values, items = values){
options <- HTML(paste0(mapply(
function(value, item){
as.character(tags$option(value = value, item))
}, c("",values), c("",items)
), collapse = ""))
as.character(
tags$select(
id = id, class = "form-control", multiple = "multiple", options
)
)
}
words1 <- c("apple", "banana")
words2 <- c("olive", "tomato")
js <- c(
"function(settings) {",
sprintf("var words1 = [%s];", toString(shQuote(words1))),
sprintf("var words2 = [%s];", toString(shQuote(words2))),
" var table = this.api().table();",
" $('#slct1').selectize({",
" items: words1,",
" onChange: function(value) {",
" var count = value.length;",
" table.cell(0,2).data(count);",
" }",
" });",
" $('#slct2').selectize({",
" items: words2,",
" onChange: function(value) {",
" var count = value.length;",
" table.cell(1,2).data(count);",
" }",
" });",
" Shiny.setInputValue('slct1', words1);",
" Shiny.setInputValue('slct2', words2);",
"}"
)
ui <- fluidPage(
br(),
verbatimTextOutput("words1"),
DTOutput("table"),
div( # this is a hidden selectize input whose role is to make
# available 'selectize.js'
style = "display: none;",
selectInput("id", "label", c("x", "y"))
)
)
server <- function(input, output, session) {
output[["table"]] <- renderDT({
dat <- data.frame(
FOO = c("bar", "baz"),
Words = c(
selector("slct1", words1),
selector("slct2", words2)
),
Count = c(length(words1), length(words2)),
stringsAsFactors = FALSE
)
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js),
preDrawCallback = JS(
'function() { Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS(
'function() { Shiny.bindAll(this.api().table().node()); }'
)
)
)
}, server = FALSE)
output[["words1"]] <- renderPrint({
input[["slct1"]]
})
}
shinyApp(ui, server)
EDIT
For an arbitrary number of rows:
library(shiny)
library(DT)
library(jsonlite)
selector <- function(id, values, items = values){
options <- HTML(paste0(mapply(
function(value, item){
as.character(tags$option(value = value, item))
}, c("", values), c("", items)
), collapse = ""))
as.character(
tags$select(
id = id, class = "form-control", multiple = "multiple", options
)
)
}
words <- list(
c("apple", "banana"),
c("olive", "tomato")
)
nrows <- length(words)
js <- c(
"function(settings) {",
sprintf("var nrows = %d;", nrows),
sprintf("var words = %s;", toJSON(words)),
" var table = this.api().table();",
" function selectize(i) {",
" $('#slct' + i).selectize({",
" items: words[i-1],",
" onChange: function(value) {",
" table.cell(i-1, 2).data(value.length);",
" }",
" });",
" }",
" for(var i = 1; i <= nrows; i++) {",
" selectize(i);",
" Shiny.setInputValue('slct' + i, words[i-1]);",
" }",
"}"
)
ui <- fluidPage(
br(),
verbatimTextOutput("words1"),
DTOutput("table"),
div( # this is a hidden selectize input whose role is to make
# available 'selectize.js'
style = "display: none;",
selectInput("id", "label", c("x", "y"))
)
)
server <- function(input, output, session) {
output[["table"]] <- renderDT({
dat <- data.frame(
FOO = c("bar", "baz"),
Words = vapply(
1:nrows,
function(i){
selector(paste0("slct", i), words[[i]])
},
character(1)
),
Count = lengths(words),
stringsAsFactors = FALSE
)
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js),
preDrawCallback = JS(
'function() { Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS(
'function() { Shiny.bindAll(this.api().table().node()); }'
)
)
)
}, server = FALSE)
output[["words1"]] <- renderPrint({
input[["slct1"]]
})
}
shinyApp(ui, server)
Here is another version. It uses the JavaScript library select2 instead of selectize. I find this one more convenient for the removal of the selected options: they are removed on clicking, while with selectize one needs the keyboard to remove an option.
library(shiny)
library(DT)
selector <- function(id, values, items = values){
options <- HTML(paste0(mapply(
function(value, item){
as.character(tags$option(value = value, selected = "selected", item))
}, values, items
), collapse = ""))
as.character(
tags$select(
id = id, multiple = "multiple", options
)
)
}
words <- list(
c("apple", "banana"),
c("olive", "tomato")
)
nrows <- length(words)
js <- c(
"function(settings) {",
sprintf("var nrows = %d;", nrows),
" var table = this.api().table();",
" function selectize(i) {",
" var $slct = $('#slct' + i);",
" $slct.select2({",
" width: '100%',",
" closeOnSelect: false",
" });",
" $slct.on('change', function(e) {",
" table.cell(i-1, 2).data($slct.val().length);",
" });",
" }",
" for(var i = 1; i <= nrows; i++) {",
" selectize(i);",
" }",
"}"
)
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js")
),
br(),
verbatimTextOutput("words1"),
DTOutput("table")
)
server <- function(input, output, session) {
output[["table"]] <- renderDT({
dat <- data.frame(
FOO = c("bar", "baz"),
Words = vapply(
1:nrows,
function(i){
selector(paste0("slct", i), words[[i]])
},
character(1)
),
Count = lengths(words),
stringsAsFactors = FALSE
)
datatable(
data = dat,
selection = "none",
escape = FALSE,
rownames = FALSE,
options = list(
initComplete = JS(js),
preDrawCallback = JS(
'function() { Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS(
'function() { Shiny.bindAll(this.api().table().node()); }'
)
)
)
}, server = FALSE)
output[["words1"]] <- renderPrint({
input[["slct1"]]
})
}
shinyApp(ui, server)

R Shiny update textInput fields' in DT on changing variable selection

I am building an app where the user loads an .RData data set (the file can be downloaded from here) and selects variable from a list (DT), moves it to another list (also DT) and then the available factor levels are displayed in a third DT underneath. This third DT also has a column of dynamically generated textInput fields which match the number of available factor levels for the variable where the user can add new values for the existing factor levels. The entered values are stored in a reactiveValues object. For now the object is just printed in the R console. The app looks like this:
library(shiny)
library(DT)
library(data.table)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton(id = "recodeChooseSrcFile", label = "Choose data file", title = "Navigate and select a file", multiple = FALSE),
fluidRow(
column(width = 6,
DTOutput(outputId = "recodeAllAvailableVars"),
),
column(width = 1, align = "center",
br(), br(), br(), br(), br(), br(), br(), br(), br(), br(),
uiOutput(outputId = "recodeArrowSelVarsRight"),
br(), br(),
uiOutput(outputId = "recodeArrowSelVarsLeft"),
),
column(width = 5,
DTOutput(outputId = "recodeVarsSelection"),
),
br(), br()
),
br(), br(),
DTOutput(outputId = "recodeScheme")
)
server <- function(input, output, session) {
available.volumes <- getVolumes()()
file.var.recode <- reactiveValues(loaded = NULL, var.levels = NULL)
# Select file and extract the variables.
shinyFileChoose(input, "recodeChooseSrcFile", roots = available.volumes, filetype = list(RData = "RData"))
observeEvent(eventExpr = input$recodeChooseSrcFile, {
if(length(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath) > 0) {
file.var.recode$loaded <- get(load(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath))
file.var.recode$var.levels <- Filter(Negate(is.null), lapply(X = file.var.recode$loaded, FUN = function(i) {
if(is.null(attr(x = i, which = "levels"))) {
NULL
} else {
attr(x = i, which = "levels")
}
}))
file.var.recode$loaded <- data.table(Variables = names(file.var.recode$loaded),
order_col = 1:ncol(file.var.recode$loaded))
}
}, ignoreInit = TRUE)
observe({
var.props.initial.available.vars <- data.table(Variables = as.character(), order_col = as.numeric())
var.props.initial.selected.vars <- data.table(Variables = as.character(), order_col = as.numeric())
recodeAllVars <- reactiveValues(recodeAvailVars = var.props.initial.available.vars, recodeSelectedVars = var.props.initial.selected.vars)
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})
# Render the arrow buttons for selecting the variables.
output$recodeArrowSelVarsRight <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsRight", label = NULL, icon("angle-right"), width = "50px")
}
})
output$recodeArrowSelVarsLeft <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsLeft", label = NULL, icon("angle-left"), width = "50px")
}
})
# Render the data table with the available variables.
output$recodeAllAvailableVars <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeAvailVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Render the table with the selected variables.
output$recodeVarsSelection <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeSelectedVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Observe the events of moving variables from available to selected.
observeEvent(input$recodeArrowSelVarsRight, {
req(input$recodeAllAvailableVars_rows_selected)
recodeAllVars$recodeSelectedVars <- rbind(isolate(recodeAllVars$recodeSelectedVars), recodeAllVars$recodeAvailVars[input$recodeAllAvailableVars_rows_selected, , drop = F])
recodeAllVars$recodeSelectedVars <- recodeAllVars$recodeSelectedVars[complete.cases(recodeAllVars$recodeSelectedVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeAvailVars <- isolate(recodeAllVars$recodeAvailVars[-input$recodeAllAvailableVars_rows_selected, , drop = F])
})
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars), recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
})
# Render the table with the text inputs.
initial.recode.new.values <- reactiveValues(values = NULL)
entered.new.values <- reactiveValues(values = NULL)
new.recoding.values <- reactiveValues(values = NULL)
shinyInput <- function(obj) {
tmp <- unlist(lapply(X = seq_along(obj), FUN = function(i) {
i <- paste0(textInput(inputId = paste0("numinp", i), label = NULL, value = NULL, width = "50px"))
}))
return(tmp)
}
shinyValue <- function(id, len) {
unlist(lapply(seq_len(len), function(i) {
input[[paste0(id, i)]]
}))
}
# Observe the changes in user selection and update the reactive values from above.
observe({
initial.recode.new.values$values <- unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))
entered.new.values$values <- data.table(
V1 = initial.recode.new.values$values,
V2 = initial.recode.new.values$values,
V3 = rep(x = "->", times = length(initial.recode.new.values$values)),
V4 = shinyInput(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))
)
new.recoding.values$values <- shinyValue(id = "numinp", len = length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))))
})
# Render the table with available levels and empty input fields where the user can enter his desired new values.
output$recodeScheme <- renderDT({
if(!is.null(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))) {
entered.new.values$values
} else {
return(NULL)
}
},
rownames = FALSE,
colnames = c("Available variable values", "Old", "->", "New"),
class = "cell-border stripe;compact cell-border;",
selection="none",
escape = FALSE,
options = list(
pageLength = 1500,
dom = 'BRrt',
rowCallback = JS("function(r,d) {$(r).attr('height', '40px')}"),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
observe({
print(new.recoding.values$values)
})
})
}
shinyApp(ui, server)
It all works fine when the variable is selected, the newly entered values are immediately updated and shown in the console on every key stroke. However, if the user decides to remove the variable from the DT of selected ones, the new.recoding.values$values reactive value becomes immediately NULL (as intended), but when another variable is added to the DT of selected variables, the old values for the previous variable are immediately brought back and never get updated. In addition, if the new variable has more levels than the first entered, then the last is possible to update, but not the previous ones (try entering ASBG03, then replace it with ASBG04 to see what I mean).
I don't really understand why is this happening. What I tried so far is to explicitly set the new.recoding.values$values to NULL in:
1.The observer where it is generated, before the shinyValue function is ran.
2.In the observeEvent where the right arrow button is pressed, i.e.:
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars),
recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
new.recoding.values$values <- NULL
})
UPDATE:
3.Following Tonio Liebrand's advice, I tried to update the text inputs as follow (added just after rendering the last DT):
observe({
if(nrow(entered.new.values$values) == 0) {
lapply(seq_len(length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))), function(i) {
updateTextInput(session,
input[[paste0("numinp", i)]],
value = NULL,
label = NULL)
})
}
})
None of these helped. Every time I remove the variable selected at first, the new.recoding.values$values is printed as NULL in the console, but then adding another variable new.recoding.values$values suddenly recovers the first values entered first, like it still "remembers" the first input.
I don't really understand this behavior can someone help to overcome this, i.e. really update on variable change?
Because the textFields are created within the datatable, you need to unbind before you use the table again (updateTextInput doesn't work). Using the code from this answer, I added the JS script with the unbind function and the function is called in the observer for the left arrow. Then you get a working app:
library(shiny)
library(DT)
library(data.table)
library(shinyFiles)
ui <- fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
shinyFilesButton(id = "recodeChooseSrcFile", label = "Choose data file", title = "Navigate and select a file", multiple = FALSE),
fluidRow(
column(width = 6,
DTOutput(outputId = "recodeAllAvailableVars"),
),
column(width = 1, align = "center",
br(), br(), br(), br(), br(), br(), br(), br(), br(), br(),
uiOutput(outputId = "recodeArrowSelVarsRight"),
br(), br(),
uiOutput(outputId = "recodeArrowSelVarsLeft"),
),
column(width = 5,
DTOutput(outputId = "recodeVarsSelection"),
),
br(), br()
),
br(), br(),
DTOutput(outputId = "recodeScheme")
)
server <- function(input, output, session) {
available.volumes <- getVolumes()()
file.var.recode <- reactiveValues(loaded = NULL, var.levels = NULL)
# Select file and extract the variables.
shinyFileChoose(input, "recodeChooseSrcFile", roots = available.volumes, filetype = list(RData = "RData"))
observeEvent(eventExpr = input$recodeChooseSrcFile, {
if(length(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath) > 0) {
file.var.recode$loaded <- get(load(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath))
file.var.recode$var.levels <- Filter(Negate(is.null), lapply(X = file.var.recode$loaded, FUN = function(i) {
if(is.null(attr(x = i, which = "levels"))) {
NULL
} else {
attr(x = i, which = "levels")
}
}))
file.var.recode$loaded <- data.table(Variables = names(file.var.recode$loaded),
order_col = 1:ncol(file.var.recode$loaded))
}
}, ignoreInit = TRUE)
observe({
var.props.initial.available.vars <- data.table(Variables = as.character(), order_col = as.numeric())
var.props.initial.selected.vars <- data.table(Variables = as.character(), order_col = as.numeric())
recodeAllVars <- reactiveValues(recodeAvailVars = var.props.initial.available.vars, recodeSelectedVars = var.props.initial.selected.vars)
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})
# Render the arrow buttons for selecting the variables.
output$recodeArrowSelVarsRight <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsRight", label = NULL, icon("angle-right"), width = "50px")
}
})
output$recodeArrowSelVarsLeft <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsLeft", label = NULL, icon("angle-left"), width = "50px")
}
})
# Render the data table with the available variables.
output$recodeAllAvailableVars <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeAvailVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Render the table with the selected variables.
output$recodeVarsSelection <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeSelectedVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Observe the events of moving variables from available to selected.
observeEvent(input$recodeArrowSelVarsRight, {
req(input$recodeAllAvailableVars_rows_selected)
recodeAllVars$recodeSelectedVars <- rbind(isolate(recodeAllVars$recodeSelectedVars), recodeAllVars$recodeAvailVars[input$recodeAllAvailableVars_rows_selected, , drop = F])
recodeAllVars$recodeSelectedVars <- recodeAllVars$recodeSelectedVars[complete.cases(recodeAllVars$recodeSelectedVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeAvailVars <- isolate(recodeAllVars$recodeAvailVars[-input$recodeAllAvailableVars_rows_selected, , drop = F])
})
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars), recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
session$sendCustomMessage("unbindDT", "recodeScheme")
})
# Render the table with the text inputs.
initial.recode.new.values <- reactiveValues(values = NULL)
entered.new.values <- reactiveValues(values = NULL)
new.recoding.values <- reactiveValues(values = NULL)
shinyInput <- function(obj) {
tmp <- unlist(lapply(X = seq_along(obj), FUN = function(i) {
i <- paste0(textInput(inputId = paste0("numinp", i), label = NULL, value = NULL, width = "50px"))
}))
return(tmp)
}
shinyValue <- function(id, len) {
unlist(lapply(seq_len(len), function(i) {
input[[paste0(id, i)]]
}))
}
# Observe the changes in user selection and update the reactive values from above.
observe({
initial.recode.new.values$values <- unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))
entered.new.values$values <- data.table(
V1 = initial.recode.new.values$values,
V2 = initial.recode.new.values$values,
V3 = rep(x = "->", times = length(initial.recode.new.values$values)),
V4 = shinyInput(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))
)
new.recoding.values$values <- shinyValue(id = "numinp", len = length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))))
})
# Render the table with available levels and empty input fields where the user can enter his desired new values.
output$recodeScheme <- renderDT({
if(!is.null(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))) {
entered.new.values$values
} else {
return(NULL)
}
},
rownames = FALSE,
colnames = c("Available variable values", "Old", "->", "New"),
class = "cell-border stripe;compact cell-border;",
selection="none",
escape = FALSE,
options = list(
pageLength = 1500,
dom = 'BRrt',
rowCallback = JS("function(r,d) {$(r).attr('height', '40px')}"),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
observe({
print(new.recoding.values$values)
})
})
}
shinyApp(ui, server)
However, I recommend you to read more about reactivity, e.g. here. You use a lot of observers, and you nest them. I don't recommend that, because this can lead to strange behaviour. Also, try to use more reactive/reactiveExpression, because observe/observeEvent can make your app slower. Before I found the correct solution, I tried to unnest your code a bit, and it still works! That shows that you had complexity in your app you actually don't need:
library(shiny)
library(DT)
library(data.table)
library(shinyFiles)
# additional functions
shinyInput <- function(obj) {
tmp <- unlist(lapply(X = seq_along(obj), FUN = function(i) {
i <- paste0(textInput(inputId = paste0("numinp", i), label = NULL, value = NULL, width = "50px"))
}))
return(tmp)
}
shinyValue <- function(id, len, input) {
unlist(lapply(seq_len(len), function(i) {
input[[paste0(id, i)]]
}))
}
ui <- fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
shinyFilesButton(id = "recodeChooseSrcFile", label = "Choose data file", title = "Navigate and select a file", multiple = FALSE),
fluidRow(
column(width = 6,
DTOutput(outputId = "recodeAllAvailableVars"),
),
column(width = 1, align = "center",
br(), br(), br(), br(), br(), br(), br(), br(), br(), br(),
uiOutput(outputId = "recodeArrowSelVarsRight"),
br(), br(),
uiOutput(outputId = "recodeArrowSelVarsLeft"),
),
column(width = 5,
DTOutput(outputId = "recodeVarsSelection"),
),
br(), br()
),
br(), br(),
DTOutput(outputId = "recodeScheme")
)
server <- function(input, output, session) {
available.volumes <- getVolumes()()
file.var.recode <- reactiveValues(loaded = NULL, var.levels = NULL)
# define variables
# Render the table with the text inputs.
initial.recode.new.values <- reactiveValues(values = NULL)
entered.new.values <- reactiveValues(values = NULL)
new.recoding.values <- reactiveValues(values = NULL)
# Select file and extract the variables.
shinyFileChoose(input, "recodeChooseSrcFile", roots = available.volumes, filetype = list(RData = "RData"))
observeEvent(eventExpr = input$recodeChooseSrcFile, {
if(length(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath) > 0) {
file.var.recode$loaded <- get(load(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath))
file.var.recode$var.levels <- Filter(Negate(is.null), lapply(X = file.var.recode$loaded, FUN = function(i) {
if(is.null(attr(x = i, which = "levels"))) {
NULL
} else {
attr(x = i, which = "levels")
}
}))
file.var.recode$loaded <- data.table(Variables = names(file.var.recode$loaded),
order_col = 1:ncol(file.var.recode$loaded))
}
}, ignoreInit = TRUE)
recodeAllVars <- reactiveValues(recodeAvailVars = data.table(Variables = as.character(), order_col = as.numeric()),
recodeSelectedVars = data.table(Variables = as.character(), order_col = as.numeric()))
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})
# Render the arrow buttons for selecting the variables.
output$recodeArrowSelVarsRight <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsRight", label = NULL, icon("angle-right"), width = "50px")
}
})
output$recodeArrowSelVarsLeft <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsLeft", label = NULL, icon("angle-left"), width = "50px")
}
})
# Render the data table with the available variables.
output$recodeAllAvailableVars <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeAvailVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Render the table with the selected variables.
output$recodeVarsSelection <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeSelectedVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Observe the events of moving variables from available to selected.
observeEvent(input$recodeArrowSelVarsRight, {
req(input$recodeAllAvailableVars_rows_selected)
recodeAllVars$recodeSelectedVars <- rbind(isolate(recodeAllVars$recodeSelectedVars), recodeAllVars$recodeAvailVars[input$recodeAllAvailableVars_rows_selected, , drop = F])
recodeAllVars$recodeSelectedVars <- recodeAllVars$recodeSelectedVars[complete.cases(recodeAllVars$recodeSelectedVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeAvailVars <- isolate(recodeAllVars$recodeAvailVars[-input$recodeAllAvailableVars_rows_selected, , drop = F])
})
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars), recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
session$sendCustomMessage("unbindDT", "recodeScheme")
})
# Observe the changes in user selection and update the reactive values from above.
observe({
initial.recode.new.values$values <- unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))
entered.new.values$values <- data.table(
V1 = initial.recode.new.values$values,
V2 = initial.recode.new.values$values,
V3 = rep(x = "->", times = length(initial.recode.new.values$values)),
V4 = shinyInput(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))
)
new.recoding.values$values <- shinyValue(id = "numinp",
len = length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))),
input = input)
})
# Render the table with available levels and empty input fields where the user can enter his desired new values.
output$recodeScheme <- renderDT({
if(!is.null(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))) {
entered.new.values$values
} else {
return(NULL)
}
},
rownames = FALSE,
colnames = c("Available variable values", "Old", "->", "New"),
class = "cell-border stripe;compact cell-border;",
selection="none",
escape = FALSE,
options = list(
pageLength = 1500,
dom = 'BRrt',
rowCallback = JS("function(r,d) {$(r).attr('height', '40px')}"),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
observe({
print(new.recoding.values$values)
})
# end of server
}
shinyApp(ui, server)
There is still some room for improvement, e.g. you could try to use a reactive instead of observe for the following snippet:
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})

How to add new row in R Shiny dataTable without refreshing the existing selected data variable in table

I am trying to create a data filter using R Shiny and DataTables, So far I am able to add the data and when I click on Add New Filter after selecting the variables , The selected variable values is refreshed.
Please help me so when I add new row , the selected variable remains same.
Below is the code:
server.r
library(shiny)
library(shinyBS)
library(shinyjs)
library(DT)
library(rhandsontable)
shinyServer(function(input, output, session) {
myValue <- reactiveValues()
observe({
if(is.null(myValue$Filter)){
Choices<- c("a","b","c","d")
myValue$Filter = data.frame(
Variable = shinyInput(selectInput, 1:5, 'var_', cl = "dynamicInputs", label = "", choices = Choices ,selected=Choices[1]),
Filter = shinyInput(actionButton, 1:5, 'go_button_', cl=NULL, label = "Filter", onclick = goButtonOnClick()) ,
Logic = c(NA,shinyInput(selectInput, 2:5, 'logic_', cl = "dynamicInputs",label = "", choices = c("And","Or"))),
Remove = c(NA,shinyInput(actionButton, 2:5, 'remove_button_',cl=NULL, "", icon = icon("close"), onclick = removeButtonOnClick())),
stringsAsFactors = FALSE,
row.names = 1:5
)
maxId<<-5
}
})
#Functions
shinyInput <- function(FUN, objs, id,cl= NULL, ...) {
inputs <- character(length(objs))
if(is.null(cl)){
for (i in 1:length(objs)) {
inputs[i] <- as.character(FUN(paste0(id, objs[i]), ...))
}
}
else {
for (i in 1:length(objs)) {
inputs[i] <- as.character(div(class=cl,FUN(paste0(id, objs[i]), ...)))
}
}
inputs
}
goButtonOnClick <- function() {
return('Shiny.onInputChange(\"select_button\", this.id) ;
Shiny.setInputValue(\"filter_button_change\", Math.random()) ')
}
removeButtonOnClick <- function() {
return('Shiny.onInputChange(\"select_remove_button\", this.id)')
}
extractValues<-function(idSeq,objName){
unlist(lapply(idSeq,function(id){
selected<-as.numeric(id)
return(as.character(shinyValue(objName, selected)))
}))
}
# Add new Condition Row
observeEvent(input$addnewRow,{
if(is.null(myValue$Filter) || nrow(myValue$Filter)==0)
return()
idSeq<-unlist(lapply(myValue$Filter$Filter,function(x){
buttonId<-stringr::str_extract(x,paste0("go_button_","\\d+"))
num<-as.numeric(gsub("go_button_","",buttonId))
return(num)
}))
allVars<-extractValues(idSeq,"var_")
allLogic<-extractValues(idSeq,"logic_")
Choices<- c("a","b","c","d")
Variable<-unlist(lapply(1:length(idSeq),function(i){
shinyInput(selectInput, idSeq[i], 'var_', cl = "dynamicInputs", label = "", choices = Choices ,selected=allVars[i])
}))
Logic<-unlist(lapply(1:length(idSeq),function(i){
if(is.na(allLogic[i]))
return(NA)
shinyInput(selectInput, idSeq[i], 'logic_', cl = "dynamicInputs",label = "", choices = c("And","Or"),selected=allLogic[i])
}))
myValue$Filter$Variable<-Variable
myValue$Filter$Logic<-Logic
maxId<<-maxId+1
newRow <- data.frame(
Variable = shinyInput(selectInput, maxId, 'var_', cl = "dynamicInputs", label = "", choices = Choices),
Filter = shinyInput(actionButton, maxId, 'go_button_',cl=NULL, label = "Filter", onclick = goButtonOnClick() ),
Logic = shinyInput(selectInput, maxId, 'logic_',cl = "dynamicInputs", label = "", choices = c("And","Or")),
Remove = shinyInput(actionButton, maxId, 'remove_button_',cl=NULL, "", icon = icon("close"), onclick = removeButtonOnClick() ),
stringsAsFactors = FALSE
)
rownames(newRow) <- as.character(nrow(myValue$Filter)+1)
myValue$Filter <- rbind(myValue$Filter,newRow)
})
# Remove Row
observeEvent(input$select_remove_button,{
if(is.null(myValue$Filter) || nrow(myValue$Filter)==0)
return()
idSeq<-unlist(lapply(myValue$Filter$Filter,function(x){
buttonId<-stringr::str_extract(x,paste0("go_button_","\\d+"))
num<-as.numeric(gsub("go_button_","",buttonId))
return(num)
}))
allVars<-extractValues(idSeq,"var_")
allLogic<-extractValues(idSeq,"logic_")
Choices <- c("a","b","c","d")
Variable<-unlist(lapply(1:length(idSeq),function(i){
shinyInput(selectInput, idSeq[i], 'var_', cl = "dynamicInputs", label = "", choices = Choices ,selected=allVars[i])
}))
Logic<-unlist(lapply(1:length(idSeq),function(i){
if(is.na(allLogic[i]))
return(NA)
shinyInput(selectInput, idSeq[i], 'logic_', cl = "dynamicInputs",label = "", choices = c("And","Or"),selected=allLogic[i])
}))
myValue$Filter$Variable<-Variable
myValue$Filter$Logic<-Logic
rowToRemove<- which(unlist(lapply(myValue$Filter$Remove,function(x){stringr::str_extract(x,paste0("remove_button_","\\d+"))}))==input$select_remove_button)
myValue$Filter<-myValue$Filter[-rowToRemove,]
if(nrow(myValue$Filter)>0)
rownames(myValue$Filter)<-as.character(1:nrow(myValue$Filter))
})
# Display bsModal for filter
observeEvent(input$filter_button_change, {
toggleModal(session,"CustomDataFilter",toggle="open")
})
# helper function for reading selectinput
shinyValue = function(id, objs) {
unlist(lapply(objs, function(i) {
value = input[[paste0(id, i)]]
if (is.null(value))
NA
else{
value
}
}))
}
# Show Filter Table
output$mytable = DT::renderDataTable({
if(is.null(myValue$Filter))
return()
myValue$Filter
}, selection = 'none', server = FALSE, escape = FALSE, options = list(
dom = "ti",
paging = TRUE,
preDrawCallback = JS(
'function() {
Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS('function() {
Shiny.bindAll(this.api().table().node()); } ')
))
})
ui.r
library(shiny)
library(shinyBS)
library(shinyjs)
library(DT)
library(rhandsontable)
shinyUI(fluidPage(
tags$head(tags$script(HTML("
$(document).on('change', '.dynamicInputs .selector select', function(){
Shiny.setInputValue('lastSelectId', this.id, {priority: 'event'});
});
"))),
tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbind-DT', function(id) {
Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
})"
)
),
tags$button(
id = "reset_button",
class="btn action-button",
icon("close")
),
bsModal("CustomDataFilter","Settings","go_CustomDataFilter_Settings",size="large",
uiOutput("FilterDataSettings")
),
bsModal("CustomDataFormula","Settings","go_CustomDataFormula_Settings",size="large",
uiOutput("CustomDataFormula")
),
actionButton("addnewRow"," Add New Filter "),
DT::dataTableOutput('mytable')
)
)

Resources