Modify cell in a R DT::datatable based on previous row - r

I have a shiny app where I would like to modify the data displayed and/or the attributes of a cell based on the value of the same cell in the previous row.
In my code I have formatted whole rows based on the value of data[0] in rowCallback.
output$result <- DT::renderDataTable(tabledata(),
class = c('compact'),
rownames = FALSE,
server = FALSE,
escape = TRUE,
extensions = options = list(
rowCallback=JS("
function (row, data, index) {
var string=data[1], substring = 'sub total';
if (data[0]=='Grand Total') {
$(row).css('background-color', '#DEDEDE'), $(row).css('font-weight', 'bold') ;
}
else if (data[0].includes('sub total')) {
$(row).css('font-weight', 'bold');
}
}"
)
)
)
Can I achieve a modification of the data[0] cell based on the value of the same cell in the previous row using one of the callback functions?

So I changed tack and used the following drawCallback call
drawCallback=JS(" function ( settings ) {
var api = this.api();
var mydata = api.rows( {page:'current'} ).data();
var last=null;
api.column(0,{page:'current'}).data().each( function ( value, index ) {
if ( value == last) {
mydata[index][0] = ''
api.rows({ page: 'current' }).invalidate();
}
last=value;
});
}"
)

Related

Paste into selectizeInput with high number of options

I want to be able to paste into selectizeInput to select multiple items at once. There are good solutions for this:
https://github.com/rstudio/shiny/issues/1663
The issue is that my list contains huge amounts of items, so the app will lag like crazy and sometimes crash. Setting maxOptions = 50 takes care of the lag, but pasting something that's not within the first 100 items, will only return "undefined", i.e., I can paste gene_1,gene_2,gene_3, but not gene_51,gene_52,gene_53 when all items are included.
Any ideas? Could you grep the list of items in the return function that would take some more time but not make the app lag like crazy?
genes <- paste0("gene_",1:50000)
ui = fluidPage(
selectizeInput("x", "Paste multiple genes:",
genes[1:100],
multiple = TRUE,
options = list(maxOptions = 50,
splitOn = I("(function() { return /[,;]/; })()"),
create = I("function(input, callback){
return {
value: input,
text: input
};
}")
)
),actionButton("button","update")
)
server<- function(input, output,session){
observeEvent(input$button,{
updateSelectizeInput(session,"x", "Paste multiple genes:",
genes,
server = TRUE,
options = list(maxOptions = 50,
splitOn = I("(function() { return /[,;]/; })()"),
create = I("function(input, callback){
return {
value: input,
text: input
};
}")
)
)
})
}
shinyApp(ui=ui, server = server)

I need to write this data to a .csv file

I'm using the following query and trying to write the results to a .csv file:
.libPaths("G:/R/R-3.6.1/library")
library(mongolite)
library(data.table)
library(dplyr)
###Path to list of search terms
#------------------------------------------------------------------------------------------------------------#
#Input the words you want to search in a text file, and call the text file below in place of ".txt"
#-------------------------------------------------------------------------------------------------------------#
searchStrings = readLines("//int/elm/Work/Text Analytics/Opportunities/New Folder/terms.txt")
searchCounts = data.table(searchString = searchStrings, count = 0)
db <- mongo(collection = "2020Emails", db = "textAnalytics", verbose = TRUE,
url = "mongodb://user:m0ng0b0ng0#interactionsprojection:7999/textAnalytics")
firstQuery = TRUE
#Update this JSON if if you want different fields returned
fieldsjson = '{"DocEid" : true,
"RequestType" : true,
"FromEmailAddress" : true,
"ReceiptTime" : true,
"RawBodyText" : true,
"CMF" : true,
"ReplyTo" : true,
"InboundMode" : true,
"PartNumbers" : true,
"_id": false}'
for (term in searchStrings) {
queryString = paste0('{"$text" : { "$search" : "\\"',term,'\\"" }}')
if (firstQuery == TRUE) {
results = NULL
results = data.table(db$find(query = queryString, fields = fieldsjson))
if (nrow(results) > 0) {
results[, searchString := term]
searchCounts[searchString == term, count := nrow(results)]
firstQuery = FALSE
}
} else {
tempdt = data.table(db$find(query = queryString, fields = fieldsjson))
if (nrow(tempdt) > 0) {
tempdt[, searchString := term]
results = rbind(results,tempdt, fill= TRUE)
searchCounts[searchString == term, count := nrow(tempdt)]
}
}
}
View(results)
#------------------------------------------------------------------------------#
# Specify the location and new file name where the results will be stored below
#------------------------------------------------------------------------------#
fwrite(results, "C:/Results/terms.csv")
I am getting the following error when trying to write this to a .csv. file.
Error in fwrite(results, "C:/Results/terms.csv") :
Row 1 of list column is type 'list' - not yet implemented. fwrite() can write list columns containing items which are atomic vectors of type logical, integer, integer64, double, complex and character.

How to remove observeEvent in Shiny?

I have function that show modal with one input to change name that execute function when save button is clicked and I have validation bind to input:
editNameDialog <- function(input, value, name, save) {
showModal(modalDialog(
textInput("modalNewName", paste("Enter", name, ":"), value),
title = paste("Edit", name),
easyClose = TRUE,
footer = div(
modalButton("Cancel"),
actionButton("modalSaveName", "Save", class = "btn-primary")
)
))
validName <- function() {
nchar(input$newName) > 5
}
observeEvent(input$modalNewName, {
if (validName()) {
shinyjs::enable("modalSaveName")
} else {
shinyjs::disable("modalSaveName")
}
});
observeEvent(input$modalSaveName, {
save(input$modalNewName)
removeModal()
}, ignoreInit = TRUE, once = TRUE)
}
I call this function like this (from server.R):
editNameDialog(input, "default value", "enter name", function(value) {
other$name <- value
})
it work but when I execute the function two times the observer input$modalNewName is executed two times for each keypress. How can I remove the event before adding new one?
I've fixed the issue by adding validation in javascript:
function observeModalName() {
var save = $('#modalSaveName');
$('#modalNewName').off('keyup').on('keyup', function() {
var self = $(this);
if (self.val().length < 5) {
save.disable();
self.error({message: 'Name must be at least 5 characters long'});
} else {
save.enable();
self.error('remove');
}
});
}
and executed:
shinyjs::runjs("observeModalName()")

returning value from DT with javascript

I'd like to add one more row to the answer posted here:
Determine if DT datatable is clicked in shiny app
Specifically I want to return the "a" field of the row that is clicked so I added
var a = table.rows[row_].cells[col].innerhtml
but nothing happens.
Here is the code. Is it possible?
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 a = table.rows[row_].cells[col].innerhtml
var data = [row_, col, rnd,a];
Shiny.onInputChange('rows',data );
});")
)}
)
observeEvent(input$rows, {
print(input$rows)
#print(Sys.time())
})}
))
Thank you.
If you want to look at clicks from rows, you could use the following callback function:
callback=JS("table.on('click.dt', 'tr', function() {
var data=table.row(this).data();
Shiny.onInputChange('rows',data[0]);
});")
This looks at click events on row (tr) and gets the data directly then returns the value in the first column.

Shiny saving URL state subpages and tabs

I would like to have a shiny website that keeps the dynamic choices in the URL as output, so you can copy and share the URL.
I took this code as an example:
https://gist.github.com/amackey/6841cf03e54d021175f0
And modified it to my case, which is a webpage with a navbarPage and multiple tabs per element in the bar.
What I would like is the URL to direct the user to the right element
in the first level tabPanel, and the right tab in the second level
tabPanel.
This is, if the user has navigated to "Delta Foxtrot" and then to
"Hotel", then changed the parameters to
#beverage=Tea;milk=TRUE;sugarLumps=3;customer=mycustomer, I would
like the URL to send the user to "Delta Foxtrot" -> "Hotel", instead
of starting at the first tab of the first panel element.
Ideally I would like a working example, since everything I tried so far hasn't worked.
Any ideas?
# ui.R
library(shiny)
hashProxy <- function(inputoutputID) {
div(id=inputoutputID,class=inputoutputID,tag("div",""));
}
# Define UI for shiny d3 chatter application
shinyUI(navbarPage('URLtests', id="page", collapsable=TRUE, inverse=FALSE,
tabPanel("Alfa Bravo",
tabsetPanel(
tabPanel("Charlie",
tags$p("Nothing to see here. Everything is in the 'Delta Foxtrot' 'Hotel' tab")
)
)
)
,tabPanel("Delta Foxtrot",
tabsetPanel(
tabPanel("Golf",
tags$p("Nothing to see here. Everything is in the 'Delta Foxtrot' 'Hotel' tab")
)
,tabPanel("Hotel",
tags$p("This widget is a demonstration of how to preserve input state across sessions, using the URL hash."),
selectInput("beverage", "Choose a beverage:",
choices = c("Tea", "Coffee", "Cocoa")),
checkboxInput("milk", "Milk"),
sliderInput("sugarLumps", "Sugar Lumps:",
min=0, max=10, value=3),
textInput("customer", "Your Name:"),
includeHTML("URL.js"),
h3(textOutput("order")),
hashProxy("hash")
)
)
)
))
# server.R
library(shiny)
url_fields_to_sync <- c("beverage","milk","sugarLumps","customer");
# Define server logic required to respond to d3 requests
shinyServer(function(input, output, clientData) {
# Generate a plot of the requested variable against mpg and only
# include outliers if requested
output$order <- reactiveText(function() {
paste(input$beverage,
if(input$milk) "with milk" else ", black",
"and",
if (input$sugarLumps == 0) "no" else input$sugarLumps,
"sugar lumps",
"for",
if (input$customer == "") "next customer" else input$customer)
})
firstTime <- TRUE
output$hash <- reactiveText(function() {
newHash = paste(collapse=";",
Map(function(field) {
paste(sep="=",
field,
input[[field]])
},
url_fields_to_sync))
# the VERY FIRST time we pass the input hash up.
return(
if (!firstTime) {
newHash
} else {
if (is.null(input$hash)) {
NULL
} else {
firstTime<<-F;
isolate(input$hash)
}
}
)
})
})
# URL.js
<script type="text/javascript">
(function(){
this.countValue=0;
var changeInputsFromHash = function(newHash) {
// get hash OUTPUT
var hashVal = $(newHash).data().shinyInputBinding.getValue($(newHash))
if (hashVal == "") return
// get values encoded in hash
var keyVals = hashVal.substring(1).split(";").map(function(x){return x.split("=")})
// find input bindings corresponding to them
keyVals.map(function(x) {
var el=$("#"+x[0])
if (el.length > 0 && el.val() != x[1]) {
console.log("Attempting to update input " + x[0] + " with value " + x[1]);
if (el.attr("type") == "checkbox") {
el.prop('checked',x[1]=="TRUE")
el.change()
} else if(el.attr("type") == "radio") {
console.log("I don't know how to update radios")
} else if(el.attr("type") == "slider") {
// This case should be setValue but it's not implemented in shiny
el.slider("value",x[1])
//el.change()
} else {
el.data().shinyInputBinding.setValue(el[0],x[1])
el.change()
}
}
})
}
var HashOutputBinding = new Shiny.OutputBinding();
$.extend(HashOutputBinding, {
find: function(scope) {
return $(scope).find(".hash");
},
renderError: function(el,error) {
console.log("Shiny app failed to calculate new hash");
},
renderValue: function(el,data) {
console.log("Updated hash");
document.location.hash=data;
changeInputsFromHash(el);
}
});
Shiny.outputBindings.register(HashOutputBinding);
var HashInputBinding = new Shiny.InputBinding();
$.extend(HashInputBinding, {
find: function(scope) {
return $(scope).find(".hash");
},
getValue: function(el) {
return document.location.hash;
},
subscribe: function(el, callback) {
window.addEventListener("hashchange",
function(e) {
changeInputsFromHash(el);
callback();
}
, false);
}
});
Shiny.inputBindings.register(HashInputBinding);
})()
</script>
EDITED: I ran the example code in the answer, but couldn't get it to work. See screenshot.
UPDATE
Shiny .14 now available on CRAN supports saving app state in a URL. See this article
This answer is a more in-depth answer than my first that uses the entire sample code provided by OP. I've decided to add it as a new answer in light of the bounty. My original answer used a simplified version of this so that someone else coming to the answer wouldn't have to read through any extraneous code to find what they're looking for. Hopefully, this extended version will clear up any difficulties you're having. Parts I've added to your R code are surrounded with ### ... ###.
server.r
# server.R
library(shiny)
url_fields_to_sync <- c("beverage","milk","sugarLumps","customer");
# Define server logic required to respond to d3 requests
shinyServer(function(input, output, session) { # session is the common name for this variable, not clientData
# Generate a plot of the requested variable against mpg and only
# include outliers if requested
output$order <- reactiveText(function() {
paste(input$beverage,
if(input$milk) "with milk" else ", black",
"and",
if (input$sugarLumps == 0) "no" else input$sugarLumps,
"sugar lumps",
"for",
if (input$customer == "") "next customer" else input$customer)
})
firstTime <- TRUE
output$hash <- reactiveText(function() {
newHash = paste(collapse=";",
Map(function(field) {
paste(sep="=",
field,
input[[field]])
},
url_fields_to_sync))
# the VERY FIRST time we pass the input hash up.
return(
if (!firstTime) {
newHash
} else {
if (is.null(input$hash)) {
NULL
} else {
firstTime<<-F;
isolate(input$hash)
}
}
)
})
###
# whenever your input values change, including the navbar and tabpanels, send
# a message to the client to update the URL with the input variables.
# setURL is defined in url_handler.js
observe({
reactlist <- reactiveValuesToList(input)
reactvals <- grep("^ss-|^shiny-", names(reactlist), value=TRUE, invert=TRUE) # strip shiny related URL parameters
reactstr <- lapply(reactlist[reactvals], as.character) # handle conversion of special data types
session$sendCustomMessage(type='setURL', reactstr)
})
observe({ # this observer executes once, when the page loads
# data is a list when an entry for each variable specified
# in the URL. We'll assume the possibility of the following
# variables, which may or may not be present:
# nav= The navbar tab desired (either Alfa Bravo or Delta Foxtrot)
# tab= The desired tab within the specified nav bar tab, e.g., Golf or Hotel
# beverage= The desired beverage selection
# sugar= The desired number of sugar lumps
#
# If any of these variables aren't specified, they won't be used, and
# the tabs and inputs will remain at their default value.
data <- parseQueryString(session$clientData$url_search)
# the navbar tab and tabpanel variables are two variables
# we have to pass to the client for the update to take place
# if nav is defined, send a message to the client to set the nav tab
if (! is.null(data$page)) {
session$sendCustomMessage(type='setNavbar', data)
}
# if the tab variable is defined, send a message to client to update the tab
if (any(sapply(data[c('alfa_bravo_tabs', 'delta_foxtrot_tabs')], Negate(is.null)))) {
session$sendCustomMessage(type='setTab', data)
}
# the rest of the variables can be set with shiny's update* methods
if (! is.null(data$beverage)) { # if a variable isn't specified, it will be NULL
updateSelectInput(session, 'beverage', selected=data$beverage)
}
if (! is.null(data$sugarLumps)) {
sugar <- as.numeric(data$sugarLumps) # variables come in as character, update to numeric
updateNumericInput(session, 'sugarLumps', value=sugar)
}
})
###
})
ui.r
library(shiny)
hashProxy <- function(inputoutputID) {
div(id=inputoutputID,class=inputoutputID,tag("div",""));
}
# Define UI for shiny d3 chatter application
shinyUI(navbarPage('URLtests', id="page", collapsable=TRUE, inverse=FALSE,
tabPanel("Alfa Bravo",
tabsetPanel(
###
id='alfa_bravo_tabs', # you need to set an ID for your tabpanels
###
tabPanel("Charlie",
tags$p("Nothing to see here. Everything is in the 'Delta Foxtrot' 'Hotel' tab")
)
)
)
,tabPanel("Delta Foxtrot",
tabsetPanel(
###
id='delta_foxtrot_tabs', # you need to set an ID for your tabpanels
###
tabPanel("Golf",
tags$p("Nothing to see here. Everything is in the 'Delta Foxtrot' 'Hotel' tab")
)
,tabPanel("Hotel", id='hotel',
tags$p("This widget is a demonstration of how to preserve input state across sessions, using the URL hash."),
selectInput("beverage", "Choose a beverage:",
choices = c("Tea", "Coffee", "Cocoa")),
checkboxInput("milk", "Milk"),
sliderInput("sugarLumps", "Sugar Lumps:",
min=0, max=10, value=3),
textInput("customer", "Your Name:"),
#includeHTML("URL.js"),
###
includeHTML('url_handler.js'), # include the new script
###
h3(textOutput("order")),
hashProxy("hash")
)
)
)
))
url_handler.js
<script>
Shiny.addCustomMessageHandler('setNavbar',
function(data) {
// create a reference to the desired navbar tab. page is the
// id of the navbarPage. a:contains says look for
// the subelement that contains the contents of data.nav
var nav_ref = '#page a:contains(\"' + data.page + '\")';
$(nav_ref).tab('show');
}
)
Shiny.addCustomMessageHandler('setTab',
function(data) {
// pick the right tabpanel ID based on the value of data.nav
if (data.page == 'Alfa Bravo') {
var tabpanel_id = 'alfa_bravo_tabs';
} else {
var tabpanel_id = 'delta_foxtrot_tabs';
}
// combine this with a reference to the desired tab itself.
var tab_ref = '#' + tabpanel_id + ' a:contains(\"' + data[tabpanel_id] + '\")';
$(tab_ref).tab('show');
}
)
Shiny.addCustomMessageHandler('setURL',
function(data) {
// make each key and value URL safe (replacing spaces, etc.), then join
// them and put them in the URL
var search_terms = [];
for (var key in data) {
search_terms.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
window.history.pushState('object or string', 'Title', '/?' + search_terms.join('&'));
}
);
</script>
To test this, call runApp(port=5678) in the directory with your source files. By default, no parameters are specified in the URL, so this will default to the first navbar item and the first tab within that item. To test it with URL parameters, point your browser to: http://127.0.0.1:5678/?nav=Delta%20Foxtrot&tab=Hotel&beverage=Coffee. This should point you to the second navbar tab and the second tab in that navbar item with coffee as the selected beverage.
Here's an example demonstrating how to update the navbar selection, tabset selection, and widget selection using variables defined in the URL
ui <- navbarPage('TEST', id='page', collapsable=TRUE, inverse=FALSE,
# define a message handler that will receive the variables on the client side
# from the server and update the page accordingly.
tags$head(tags$script("
Shiny.addCustomMessageHandler('updateSelections',
function(data) {
var nav_ref = '#page a:contains(\"' + data.nav + '\")';
var tabpanel_id = data.nav == 'Alpha' ? '#alpha_tabs' : '#beta_tabs';
var tab_ref = tabpanel_id + ' a:contains(\"' + data.tab + '\")';
$(nav_ref).tab('show');
$(tab_ref).tab('show');
}
)
")),
tabPanel('Alpha',
tabsetPanel(id='alpha_tabs',
tabPanel('Tab')
)
),
tabPanel('Beta',
tabsetPanel(id='beta_tabs',
tabPanel('Golf'),
tabPanel('Hotel',
selectInput("beverage", "Choose a beverage:", choices = c("Tea", "Coffee", "Cocoa"))
)
)
)
)
server <- function(input, output, session) {
observe({
data <- parseQueryString(session$clientData$url_search)
session$sendCustomMessage(type='updateSelections', data)
updateSelectInput(session, 'beverage', selected=data$beverage)
})
}
runApp(list(ui=ui, server=server), port=5678, launch.browser=FALSE)
Point your browser to this URL after starting the app: http://127.0.0.1:5678/?nav=Beta&tab=Hotel&beverage=Coffee

Resources