How to download datatable after editing in shiny - r

I have about 20 thousand images in a datatable in a shiny application. I want to delete images I dont like and then download the resulting datatable (just with pathnames to the images I want).
I have managed to see a dataframe with each row showing me an image. I can delete rows I dont like. I'd like now to be able to download the dataframe (just with the paths of the files rather than the actual images) into a csv. I don't seem to be able to do this using the downloadHandler. What am I missing?
Here is my code:
server.R
library(shiny)
library(shinydashboard)
library(data.table)
library(DT)
server<-shinyServer(function(input, output) {
vals<-reactiveValues()
vals$Data<-data.table(
df
)
output$MainBody<-renderUI({
fluidPage(
box(width=12,
hr(),
column(12,dataTableOutput("Main_table")),
tags$script(HTML('$(document).on("click", "input", function () {
var checkboxes = document.getElementsByName("row_selected");
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].value);
}
}
Shiny.onInputChange("checked_rows",checkboxesChecked);
})')),
tags$script("$(document).on('click', '#Main_table button', function () {
Shiny.onInputChange('lastClickId',this.id);
Shiny.onInputChange('lastClick', Math.random())
});")
)
)
})
output$downloadData <- downloadHandler(
filename = function() {
paste(input$Main_table, ".csv", sep = "")
},
content = function(file) {
write.csv(vals$Data, file, row.names = FALSE)
}
)
output$Main_table<-renderDataTable({
DT=vals$Data
datatable(DT,
escape=F)}
)
})
ui.R
ui<-fluidPage(dashboardHeader(disable = T),
dashboardSidebar(disable = T),
downloadLink("downloadData", "Download"),
dashboardBody(uiOutput("MainBody")
)
)
# Run the application
shinyApp(ui = ui, server = server)

The problem is that df is a function from library(stats). Please see ?df. Please, never use df as a variable name - this only causes confusion; take DF instead. You wrapped this function into data.table() and passed it into a reactiveValue (see the print() I added).
This construct is treated as a list() by write.csv() and it doesn't know what do do with a list() causing the following error:
Error in write.table: unimplemented type 'list' in 'EncodeElement'
Accordingly you might want to fix your data.table initialization by e.g. passing NULL instead of df:
library(shiny)
library(shinydashboard)
library(data.table)
library(DT)
server <- shinyServer(function(input, output) {
vals <- reactiveValues(myTabData = data.table(NULL))
vals$Data <- data.table(df)
print(paste("myData:", isolate(vals$Data)))
output$MainBody <- renderUI({
fluidPage(box(
width = 12,
hr(),
column(12, dataTableOutput("Main_table")),
tags$script(
HTML(
'$(document).on("click", "input", function () {
var checkboxes = document.getElementsByName("row_selected");
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].value);
}
}
Shiny.onInputChange("checked_rows",checkboxesChecked);})'
)
),
tags$script(
"$(document).on('click', '#Main_table button', function () {
Shiny.onInputChange('lastClickId',this.id);
Shiny.onInputChange('lastClick', Math.random())});"
)
))
})
output$downloadData <- downloadHandler(
filename = function() {
"Main_table.csv"
},
content = function(file) {
write.csv(vals$myTabData, file, row.names = FALSE)
# Warning: Error in write.table: unimplemented type 'list' in 'EncodeElement'
# write.csv(vals$Data, file, row.names = FALSE)
}
)
output$Main_table <- renderDataTable({
DT = vals$Data
datatable(DT, escape = FALSE)
})
})
ui <- fluidPage(
dashboardHeader(disable = T),
dashboardSidebar(disable = T),
downloadLink("downloadData", "Download"),
dashboardBody(uiOutput("MainBody"))
)
shinyApp(ui = ui, server = server)
Furthermore you should fix your filename (input$Main_table is not existing)

Related

shinyApp - How to copy a value from selectInput and paste to a reactive table

I want to copy values from a selectInput and paste it to a reactive table. Below is an example.
library(shiny)
library(tidyverse)
library(DT)
df<-data.frame("Name" = c("apple","cherry"),
"Value" = c(5,6))
ui <- fluidPage(
uiOutput("fruit"),
fluidRow(DT::DTOutput("table1")))
server <- function(input, output, session) {
df1 <- reactiveValues(data = {df})
output$table1 = DT::renderDT({
DT::datatable(df1$data, editable = T)
})
output$fruit <- renderUI({
selectInput(
inputId = "fruit_name",
label = "Name",
multiple = TRUE,
choices = c("apple","cherry","pear", "peach","banana"),
selected = "apple"
)
})
}
shinyApp(server = server, ui = ui)
I hope to be able to choose a fruit name from selectInput, copy it, and paste it to the table under column "Name". It seems shinyApp doesn't support regular copy and paste. Anyway to do it? Thanks!
It's impossible to select the text inside an item. With the code below, you can select an item and use Ctrl-C and Ctrl-V to copy and paste it.
js <- '
document.addEventListener("copy", (event) => {
const anchorNode = document.getSelection().anchorNode;
if (anchorNode instanceof HTMLElement &&
anchorNode.classList.contains("selectize-input")) {
const items = Array.from(anchorNode.getElementsByClassName("item active"));
const string = items.map(i => i.innerText).join(", ");
event.clipboardData.setData("text/plain", string);
event.preventDefault();
}
})
'
ui <- fluidPage(
tags$head(tags$script(HTML(js))),
uiOutput("fruit"),
fluidRow(DTOutput("table1"))
)

withMathJax inside modalDialog table

I am trying to include LateX formulas inside a table and I am using the MathJack library to do so. Everthing is working smoothly outside a modalDialog, but when the table is produced within the modalDialog, it does not show as expected. I guess it has do to with what is written in the help page "It only needs to be called once in an app unless the content is rendered after the page is loaded, e.g. via renderUI(), in which case we have to call it explicitly every time we write math expressions to the output.". But I can't figure out how to solve the issue.
Here is a repex :
library(shiny)
ui <- shinyUI(
fluidPage(
withMathJax(),
actionButton("open", "Open")))
server <- function(input, output, session){
output$mytable <- renderTable({
df <- data.frame(A = c(HTML("$$\\alpha+\\beta$$"), "$$\\alpha+\\gamma$$", "$$\\alpha+\\lambda$$"),B = c(111111, 3333333, 3123.233))
df
}, sanitize.text.function = function(x) x)
observeEvent(input$open, {
showModal(modalDialog(
withMathJax(),
h2("$$\\mbox{My Math example }\\sqrt{2}$$"),
tableOutput('mytable')))
})
}
shinyApp(ui = ui, server = server)
Oddly, that works like this:
observeEvent(input$open, {
showModal(withMathJax(modalDialog(
h2("$$\\mbox{My Math example }\\sqrt{2}$$"),
withMathJax(tableOutput('mytable')))))
})
EDIT
Since there are some problems with this solution, here is a solution using KaTeX instead of MathJax:
library(shiny)
js <- "
$(document).on('shiny:value', function(event) {
if(event.name === 'mytable'){
// h2 element
var $h2 = $('#title');
var title = $h2.html();
var matches_title = title.match(/(%%+[^%]+%%)/g);
var i, code;
for(i=0; i<matches_title.length; i++){
code = matches_title[i].slice(2,-2);
title = title.replace(matches_title[i], katex.renderToString(code));
}
$h2.html(title);
$h2.css('visibility', 'visible');
// table:
var matches = event.value.match(/(%%+[^%]+%%)/g);
var newvalue = event.value;
for(i=0; i<matches.length; i++){
code = matches[i].slice(2,-2);
newvalue = newvalue.replace(matches[i], katex.renderToString(code));
}
event.value = newvalue;
}
})
"
css <- "#mytable td:nth-child(3) {display: none;}"
ui <- fluidPage(
tags$head(
tags$link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/katex#0.15.2/dist/katex.min.css", integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ", crossorigin="anonymous"),
tags$script(defer="", src="https://cdn.jsdelivr.net/npm/katex#0.15.2/dist/katex.min.js", integrity="sha384-VQ8d8WVFw0yHhCk5E8I86oOhv48xLpnDZx5T9GogA/Y84DcCKWXDmSDfn13bzFZY", crossorigin="anonymous"),
tags$script(HTML(js)),
tags$style(HTML(css))
),
titlePanel("Hello Shiny!"),
br(),
actionButton("open", "Open")
)
server <- function(input, output, session){
output$mytable <- renderTable({
data.frame(
A = c("%%\\alpha+\\beta%%", "%%\\alpha+\\gamma%%", "%%\\alpha+\\lambda%%"),
B = c(111111, 3333333, 3123.233),
` ` = rep(input$open, 3),
check.names = FALSE
)
}, sanitize.text.function = function(x) x)
observeEvent(input$open, {
showModal(modalDialog(
h2(
id = "title",
style = "visibility: hidden;",
"%%\\boxed{Math}\\sqrt{2}%%"
),
tableOutput("mytable")
))
})
}
shinyApp(ui, server)
Note that I include a reactive column in the dataframe:
` ` = rep(input$open, 3)
That's because the KaTeX rendering works only one time if I don't do that. Then I hide this column with some CSS.

How to combine multiple R modules (with submodels and uiOuput) using shinydashboard?

I'm modularizing a Shiny app I developed using shinydashboard packages. Despite it traditionally works when I use it without involving modules, I can't make it work when I try to divide it into modules and submodules. Here I would like to combine two UIs (one for the sidebar, one for the body) in order to upload a dataset from the sidebar and show it into the body.
I'd be very glad if anybody could provide me some help with this.
Here is the code of the general Shiny app:
library(shiny)
library(excelR)
library(vroom)
library(readxl)
library(janitor)
library(dplyr)
library(shinydashboard)
library(shinydashboardPlus)
# # load separate module and function scripts
source("modules.R")
# app_ui
app_ui <- function() {
tagList(
shinydashboardPlus::dashboardPagePlus(
header = shinydashboardPlus::dashboardHeaderPlus(title = "module_test",
enable_rightsidebar = FALSE),
sidebar = shinydashboard::dashboardSidebar(
shinydashboard::sidebarMenu(id = "tabs",
import_sidebar_ui("import"))
),
body = shinydashboard::dashboardBody(shinydashboard::tabItems(
import_body_ui("import"))
),
rightsidebar = NULL,
title = "Module App"
)
)
}
# app_server
app_server <- function(input, output, session) {
shiny::moduleServer(id = "import", module = import_server)
}
####################################################################
run_app <- function(...) {
shiny::shinyApp(
ui = app_ui,
server = app_server)
}
#---------------------------------
run_app()
and here is the modules.R file I wrote containing the UIs for sidebar and body, plus the server:
# Import module ####
#
# Import sidebar UI
import_sidebar_ui <- function(id) {
ns <- NS(id)
shinydashboard::menuItem("Module Testing",
tabName = "tab_testing_mod",
icon = icon("th"),
tagList(
selectInput(ns("input_type"),
"Type of file:",
choices = c("Choose one" = "",".csv" = "csv",
".txt" = "txt", ".xls/.xlsx" = "xlsx"),
selected = NULL),
uiOutput(ns("inputControls")),
fileInput(ns("file"), "Data", buttonLabel = "Upload..."),
checkboxInput(ns("rownames"), "Check if 1st column contains rownames"),
checkboxInput(ns("constant"), "Remove constant columns?"),
checkboxInput(ns("empty"), "Remove empty cols?"),
actionButton(ns("bttn_import"), "Import data")
)
)
}
# Import body UI
import_body_ui <- function(id) {
ns <- NS(id)
shinydashboard::tabItem(tabName = "tab_testing_mod",
fluidRow(
h3("Imported Data"),
excelR::excelOutput(ns("preview")))
)
}
# Import server
import_server <- function(input, output, session) {
ns <- session$ns
output$inputControls <- renderUI({
tagList(
switch(input$input_type,
"csv" = textInput("delim", "Delimiter (leave blank to guess)", ""),
"txt" = textInput("delim", "Delimiter (leave blank to guess)", "")
),
switch(input$input_type,
"xlsx" = numericInput("sheet", "Sheet number", value = 1))
)
})
raw <- reactive({
req(input$file)
if (input$input_type == "csv" || input$input_type == "txt") {
delim <- if (input$delim == "") NULL else input$delim
data <- vroom::vroom(input$file$datapath, delim = delim)
} else if (input$input_type == "xlsx") {
data <- tibble::as.tibble(readxl::read_excel(input$file$datapath, sheet = input$sheet, col_names = TRUE))
} else {
return(NULL)
}
raw <- data
raw
})
tidied <- eventReactive(input$bttn_import,{
out <- raw()
if (input$empty) {
out <- janitor::remove_empty(out, "cols")
}
if (input$constant) {
out <- janitor::remove_constant(out)
}
if (input$rownames) {
out <- tibble::column_to_rownames(out, var = colnames(out[1]))
}
out <- out %>% dplyr::mutate_if(is.character,as.factor)
out
})
output$preview <- excelR::renderExcel({
excelR::excelTable(data = raw(),
colHeaders = toupper(colnames(raw())),
fullscreen = FALSE,
columnDrag = TRUE,
rowDrag = TRUE,
wordWrap = FALSE,
search =TRUE,
showToolbar = TRUE,
minDimensions = c(ncol(raw()),10)
)
})
}
It seems to me I can upload the dataset (.csv, .txt or .xlsx) files but I can't show it into the body.
I'd be very glad if you can help me, thank you very much in advance for your assistance.

Get the current image name of a slickR slideshow in shiny

Below is a shiny app which displays a slideshow of images with the slickR package. How to get the name of the current image?
library(shiny)
library(slickR)
ui <- fluidPage(
tags$div(
slickROutput("slickr", width="500px"),
style = "margin-left:100px;"
)
)
server <- function(input, output) {
imgs <- list.files("~/", pattern=".png", full.names = TRUE)
output[["slickr"]] <- renderSlickR({
slickR(imgs)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Here is a solution with a MutationObserver:
library(shiny)
library(slickR)
js <- "
$(document).ready(function(){
var ss = document.getElementById('slickr');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
var index = $(ss).find('.slick-current').data('slick-index');
Shiny.setInputValue('imageIndex', parseInt(index)+1);
});
// configuration of the observer
var config = {subtree: true, attributes: true};
// observe
observer.observe(ss, config);
})
"
ui <- fluidPage(
tags$head(
tags$script(HTML(js))
),
textOutput("imgName"),
tags$hr(),
tags$div(
slickROutput("slickr", width="500px"),
style = "margin-left:100px;"
)
)
server <- function(input, output) {
imgs <- list.files("~/", pattern=".png", full.names = TRUE)
output[["slickr"]] <- renderSlickR({
slickR(imgs)
})
output[["imgName"]] <- renderText({
paste0("CURRENT IMAGE: ", basename(imgs[input[["imageIndex"]]]))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Another solution, simpler: replace js with
js <- "
$(document).ready(function(){
$('#slickr').on('setPosition', function(event, slick) {
var index = slick.currentSlide + 1;
Shiny.setInputValue('imageIndex', index);
});
})"
Maybe something like this workaround?
I am using the index of the image and get the basename of the imagelist.
library(shiny)
library(slickR)
jscode <- HTML("
$(document).on('shiny:connected', function(event) {
var imagindex = 0;
Shiny.onInputChange('slickin', imagindex);
$(document).on('click', '.slick-arrow', function(event) {
var imagindex = $('.slick-active')[0].attributes[1].value;
Shiny.onInputChange('slickin', imagindex);
});
$(document).on('click', '.slick-dots', function(event) {
var imagindex = $('.slick-active')[0].attributes[1].value;
Shiny.onInputChange('slickin', imagindex);
});
});
")
ui <- fluidPage(
tags$head(tags$script(jscode)),
tags$div(
slickROutput("slickr", width="500px"),
style = "margin-left:100px;"
)
)
server <- function(input, output) {
imgs <- list.files(getwd(), pattern=".png", full.names = TRUE);
output[["slickr"]] <- renderSlickR({
slickR(imgs)
})
observe( {
req(input$slickin)
print(basename(imgs[as.numeric(input$slickin) + 1]))
})
}
shinyApp(ui = ui, server = server)
The slickR shiny vignette describes the "official" way without using custom JS:
Observe the active slick
The htmlwidget is observed by shiny and information can be retrieved.
Using the output name you set for the renderSlick object in this example
it is output$slick_output
Using this you can interact server-side "on click" of the active carousel
by accessing elements in input$slick_output_current$
.clicked : The index of the clicked element
.relative_clicked: The relative position of the clicked element
.center : The index of the center element
.total : The total number of elements in the carousel
.active : The ID of the active carousel
library(shiny)
library(slickR)
# create some local images
if(!dir.exists("myimages")){
dir.create("myimages")
}
imgs <- paste0("myimages/myplot", seq_len(3), ".png")
for (myPlot in myPlots) {
png(file = myPlot, bg = "transparent")
plot(runif(10))
dev.off()
}
ui <- fluidPage(
tags$head(
tags$script(HTML(js))
),
textOutput("imgName"),
tags$hr(),
tags$div(
slickROutput("slickr", width="500px"),
style = "margin-left:100px;"
)
)
server <- function(input, output) {
output[["slickr"]] <- renderSlickR({
slickR(imgs)
})
output[["imgName"]] <- renderText({
paste0("CURRENT IMAGE: ", basename(imgs[input$slickr_current$.center]))
})
}
# Run the application
shinyApp(ui = ui, server = server)
Here's a solution from one of the slickR vignettes:
slickR(obj = nba_player_logo$uri[1:2], height = 100, width = "95%") %synch%
( slickR(nba_player_logo$name[1:2], slideType = 'p') + settings(arrows = FALSE) )
Worked great for me.

Determine if DT datatable is clicked in shiny app

Here is a working example of my best attempt to get table click event:
library(shiny)
library(DT)
runApp(shinyApp(
ui = fluidPage(DT::dataTableOutput('table')),
server = function(input, output, session) {
output$table <- DT::renderDataTable({
dt <- data.frame(a = 1)
datatable(dt, rownames = FALSE, selection = 'none')
})
observeEvent(input$table_cell_clicked, {
print(Sys.time())
})}
))
The problem is that observeEvent reacts only if user clicks on the cell which differs from previously clicked. Is there a way to get event on any table click?
I think it s may be helpful
Try add callback with Shiny.onInputChange
and add smth which changed all time ( rnd)
smt like
JS("table.on('click.dt', 'td', function() {
var row_=table.cell(this).index().row;
var col=table.cell(this).index().column;
var rnd= Math.random();
var data = [row_, col, rnd];
Shiny.onInputChange('rows',data );
});")
and then use it like :
library(shiny)
library(DT)
runApp(shinyApp(
ui = fluidPage(DT::dataTableOutput('table')),
server = function(input, output, session) {
output$table <- DT::renderDataTable({
datatable(data.frame(a = c(1,2),b=c(2,3)), rownames = FALSE, selection = 'none', callback = JS("table.on('click.dt', 'td', function() {
var row_=table.cell(this).index().row;
var col=table.cell(this).index().column;
var rnd= Math.random();
var data = [row_, col, rnd];
Shiny.onInputChange('rows',data );
});")
)}
)
observeEvent(input$rows, {
print(input$rows)
print(Sys.time())
})}
))
Then parse all row and col from input$rows
PS. in datatables index start from 0 .

Resources