Shiny searchInput - r

Does anyone know if it is possible to create a "shinyWidget :: searchInput" designed with a grid that would allow entering only 9 digits in the field?
https://pasteboard.co/J3mduIS.png

Here is a shiny implementation of bootstrap-pincode-input.
Download the files bootstrap-pincode-input.js and bootstrap-pincode-input.css from here. Put them in the www subfolder of the folder containing the shiny app.
Also add the JS file below to the www subfolder, call it pincodeBinding.js.
var pincodeBinding = new Shiny.InputBinding();
$.extend(pincodeBinding, {
find: function (scope) {
return $(scope).find(".pincode");
},
getValue: function (el) {
return $(el).val();
},
setValue: function(el, value) {
$(el).val(value);
},
subscribe: function (el, callback) {
$(el).on("change.pincodeBinding", function (e) {
callback();
});
},
unsubscribe: function (el) {
$(el).off(".pincodeBinding");
},
initialize: function(el) {
var $el = $(el);
$el.pincodeInput({
inputs: $el.data("ndigits"),
hidedigits: $el.data("hide"),
complete: function(value, e, errorElement){
Shiny.setInputValue($el.attr("id"), value);
}
});
}
});
Shiny.inputBindings.register(pincodeBinding);
Now, the shiny app:
library(shiny)
pincodeInput <- function(inputId, width = "30%", height = "100px",
label = NULL, ndigits = 4, hideDigits = FALSE){
tags$div(style = sprintf("width: %s; height: %s;", width, height),
shiny:::shinyInputLabel(inputId, label),
tags$input(id = inputId, class = "pincode", type = "text",
`data-ndigits` = ndigits,
`data-hide` = ifelse(hideDigits, "true", "false")
)
)
}
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "bootstrap-pincode-input.css"),
tags$script(src = "bootstrap-pincode-input.js"),
tags$script(src = "pincodeBinding.js")
),
br(),
pincodeInput("pincode", label = "Enter pincode"),
br(),
h3("You entered:"),
verbatimTextOutput("pincodeValue")
)
server <- function(input, output, session){
output[["pincodeValue"]] <- renderPrint({
input[["pincode"]]
})
}
shinyApp(ui, server)
Note that the pincode input only accepts digits, not alphabetical characters. I don't know whether this is what you want?
EDIT: clearable pincode input
Add this CSS file in the www folder, name it pincode-input.css:
.clearable {
padding: 1px 6px 1px 1px;
display: inline-flex;
}
.clearable span {
cursor: pointer;
color: blue;
font-weight: bold;
visibility: hidden;
margin-left: 5px;
}
Replace pincodeBinding.js with this file:
var pincodeBinding = new Shiny.InputBinding();
$.extend(pincodeBinding, {
find: function (scope) {
return $(scope).find(".pincode");
},
getValue: function (el) {
return $(el).val();
},
setValue: function(el, value) {
$(el).val(value);
},
subscribe: function (el, callback) {
$(el).on("change.pincodeBinding", function (e) {
callback();
});
},
unsubscribe: function (el) {
$(el).off(".pincodeBinding");
},
initialize: function(el) {
var $el = $(el);
var clearBtn = el.nextElementSibling;
$el.pincodeInput({
inputs: $el.data("ndigits"),
hidedigits: $el.data("hide"),
complete: function(value, e, errorElement){
Shiny.setInputValue($el.attr("id"), value);
},
change: function(){
clearBtn.style.visibility = ($el.val().length) ? "visible" : "hidden";
}
});
clearBtn.onclick = function() {
this.style.visibility = "hidden";
$el.pincodeInput().data("plugin_pincodeInput").clear();
Shiny.setInputValue($el.attr("id"), "");
};
}
});
Shiny.inputBindings.register(pincodeBinding);
The app:
library(shiny)
pincodeInput <- function(inputId, width = "30%", height = "100px",
label = NULL, ndigits = 4, hideDigits = FALSE){
tags$div(style = sprintf("width: %s; height: %s;", width, height),
shiny:::shinyInputLabel(inputId, label),
tags$span(
class = "clearable",
tags$input(id = inputId, class = "pincode", type = "text",
`data-ndigits` = ndigits,
`data-hide` = ifelse(hideDigits, "true", "false")
),
tags$span(title = "Clear", HTML("×"))
)
)
}
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "bootstrap-pincode-input.css"),
tags$link(rel = "stylesheet", href = "pincode-input.css"),
tags$script(src = "bootstrap-pincode-input.js"),
tags$script(src = "pincodeBinding.js")
),
br(),
pincodeInput("pincode", label = "Enter pincode"),
br(),
h3("You entered:"),
verbatimTextOutput("pincodeValue")
)
server <- function(input, output, session){
output[["pincodeValue"]] <- renderPrint({
input[["pincode"]]
})
}
shinyApp(ui, server)

Related

Display a folder structure in shiny app body as a box not a pop-up

I would like to have a box in my shiny app, which user can browse to a folder structure and select a file to download.
I have tried the shinyFiles but the file selection is a pop-up window and I could just download a single file :
library(shiny)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton('files', label='File select', title='Please select a file', multiple=T) ,
verbatimTextOutput('rawInputValue'),
verbatimTextOutput('filepaths') ,
downloadButton("downloadFiles", "Download Files")
)
server <- function(input, output) {
roots = c(wd = 'H:/')
shinyFileChoose(input, 'files',
roots = roots,
filetypes=c('', 'txt' , 'gz' , 'md5' , 'pdf' , 'fasta' , 'fastq' , 'aln'))
output$rawInputValue <- renderPrint({str(input$files)})
output$filepaths <- renderPrint({parseFilePaths(roots, input$files)})
output$downloadFiles <- downloadHandler(
filename = function() {
as.character(parseFilePaths(roots, input$files)$name)
},
content = function(file) {
fullName <- as.character(parseFilePaths(roots, input$files)$datapath)
file.copy(fullName, file)
}
)
}
shinyApp(ui = ui , server = server)
What I would like is to have the file selection option like
within the ui, not as new (pop-up) window !
Here is a first version of the app I talked about in my comment. Its advantage is that the contents of a folder are loaded only when the user selects this folder, and only the first descendants are loaded, no recursion.
App folder structure:
C:\PATH\TO\MYAPP
| global.R
| server.R
| ui.R
|
\---www
navigator.css
navigator.js
File global.R:
library(shiny)
library(jsTreeR)
library(htmlwidgets)
library(magrittr)
library(shinyFiles)
roots <- c(wd = "C:/SL/MyPackages/", getVolumes()())
File server.R:
shinyServer(function(input, output, session){
shinyDirChoose(
input, "rootfolder", roots = roots,
allowDirCreate = FALSE, defaultRoot = "wd"
)
RootFolder <- eventReactive(input[["rootfolder"]], {
parseDirPath(roots, input[["rootfolder"]])
})
output[["choice"]] <- reactive({
isTruthy(RootFolder())
})
outputOptions(output, "choice", suspendWhenHidden = FALSE)
output[["navigator"]] <- renderJstree({
req(isTruthy(RootFolder()))
jstree(
nodes = list(
list(
text = RootFolder(),
type = "folder",
children = FALSE,
li_attr = list(
class = "jstree-x"
)
)
),
types = list(
folder = list(
icon = "fa fa-folder gold"
),
file = list(
icon = "far fa-file red"
)
),
checkCallback = TRUE,
theme = "default",
checkboxes = TRUE,
search = TRUE,
selectLeavesOnly = TRUE
) %>% onRender("function(el, x){tree = $(el).jstree(true);}")
})
observeEvent(input[["path"]], {
lf <- list.files(input[["path"]], full.names = TRUE)
fi <- file.info(lf, extra_cols = FALSE)
x <- list(
elem = as.list(basename(lf)),
folder = as.list(fi[["isdir"]])
)
session$sendCustomMessage("getChildren", x)
})
Paths <- reactive({
vapply(
input[["navigator_selected_paths"]], `[[`,
character(1L), "path"
)
})
output[["selections"]] <- renderPrint({
cat(Paths(), sep = "\n")
})
output[["dwnld"]] <- downloadHandler(
filename = "myfiles.zip",
content = function(file){
zip(file, files = Paths())
}
)
})
File ui.R:
shinyUI(fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "navigator.css"),
tags$script(src = "navigator.js")
),
br(),
conditionalPanel(
condition = "!output.choice",
fluidRow(
column(
width = 12,
shinyDirButton(
"rootfolder",
label = "Browse to choose a root folder",
title = "Choose a folder",
buttonType = "primary",
class = "btn-block"
)
)
)
),
conditionalPanel(
condition = "output.choice",
style = "display: none;",
fluidRow(
column(
width = 6,
jstreeOutput("navigator")
),
column(
width = 6,
tags$fieldset(
tags$legend(
tags$h1("Selections:", style = "float: left;"),
downloadButton(
"dwnld",
class = "btn-primary btn-lg",
icon = icon("save"),
style = "float: right;"
)
),
verbatimTextOutput("selections")
)
)
)
)
))
File navigator.css:
.jstree-default .jstree-x.jstree-closed > .jstree-icon.jstree-ocl,
.jstree-default .jstree-x.jstree-leaf > .jstree-icon.jstree-ocl {
background-position: -100px -4px;
}
.red {
color: red;
}
.gold {
color: gold;
}
.jstree-proton {
font-weight: bold;
}
.jstree-anchor {
font-size: medium;
}
File navigator.js:
var tree;
$(document).ready(function () {
var Children = null;
Shiny.addCustomMessageHandler("getChildren", function (x) {
Children = x;
});
$("#navigator").on("click", "li.jstree-x > i", function (e) {
var $li = $(this).parent();
if (!$li.hasClass("jstree-x")) {
alert("that should not happen...");
return;
}
var id = $li.attr("id");
var node = tree.get_node(id);
if (tree.is_leaf(node) && node.original.type === "folder") {
var path = tree.get_path(node, "/");
Shiny.setInputValue("path", path);
var interval = setInterval(function () {
if (Children !== null) {
clearInterval(interval);
for (var i = 0; i < Children.elem.length; i++) {
var isdir = Children.folder[i];
var newnode = tree.create_node(id, {
text: Children.elem[i],
type: isdir ? "folder" : "file",
children: false,
li_attr: isdir ? { class: "jstree-x" } : null
});
}
Children = null;
setTimeout(function () {
tree.open_node(id);
}, 10);
}
}, 100);
}
});
});
(I am the author of jsTreeR and I think I will do a Shiny module for this folder navigator and include it in the package.)
EDIT
I improved the app and it uses the proton theme now, which looks more pretty to me:
To use this app, you first need the updated version of the package:
remotes::install_github("stla/jsTreeR")
There are some changes in three files:
server.R:
shinyServer(function(input, output, session){
shinyDirChoose(
input, "rootfolder", roots = roots,
allowDirCreate = FALSE, defaultRoot = "wd"
)
RootFolder <- eventReactive(input[["rootfolder"]], {
parseDirPath(roots, input[["rootfolder"]])
})
output[["choice"]] <- reactive({
isTruthy(RootFolder())
})
outputOptions(output, "choice", suspendWhenHidden = FALSE)
output[["navigator"]] <- renderJstree({
req(isTruthy(RootFolder()))
jstree(
nodes = list(
list(
text = RootFolder(),
type = "folder",
children = FALSE,
li_attr = list(
class = "jstree-x"
)
)
),
types = list(
folder = list(
icon = "fa fa-folder gold"
),
file = list(
icon = "far fa-file red"
)
),
checkCallback = TRUE,
theme = "proton",
checkboxes = TRUE,
search = TRUE,
selectLeavesOnly = TRUE
)
})
observeEvent(input[["path"]], {
lf <- list.files(input[["path"]], full.names = TRUE)
fi <- file.info(lf, extra_cols = FALSE)
x <- list(
elem = as.list(basename(lf)),
folder = as.list(fi[["isdir"]])
)
session$sendCustomMessage("getChildren", x)
})
Paths <- reactive({
vapply(
input[["navigator_selected_paths"]], `[[`,
character(1L), "path"
)
})
output[["selections"]] <- renderPrint({
cat(Paths(), sep = "\n")
})
output[["dwnld"]] <- downloadHandler(
filename = "myfiles.zip",
content = function(file){
zip(file, files = Paths())
}
)
})
navigator.css:
.jstree-proton {
font-weight: bold;
}
.jstree-anchor {
font-size: medium;
}
.jstree-proton .jstree-x.jstree-closed > .jstree-icon.jstree-ocl,
.jstree-proton .jstree-x.jstree-leaf > .jstree-icon.jstree-ocl {
background-position: -101px -5px;
}
.jstree-proton .jstree-checkbox.jstree-checkbox-disabled {
background-position: -37px -69px;
}
.red {
color: red;
}
.gold {
color: gold;
}
navigator.js:
$(document).ready(function () {
var tree;
var Children = null;
Shiny.addCustomMessageHandler("getChildren", function (x) {
Children = x;
});
$navigator = $("#navigator");
$navigator.one("ready.jstree", function (e, data) {
tree = data.instance;
tree.disable_checkbox("j1_1");
tree.disable_node("j1_1");
});
$navigator.on("after_open.jstree", function (e, data) {
tree.enable_checkbox(data.node);
tree.enable_node(data.node);
});
$navigator.on("after_close.jstree", function (e, data) {
tree.disable_checkbox(data.node);
tree.disable_node(data.node);
});
$navigator.on("click", "li.jstree-x > i", function (e) {
var $li = $(this).parent();
if (!$li.hasClass("jstree-x")) {
alert("that should not happen...");
return;
}
var id = $li.attr("id");
var node = tree.get_node(id);
if (tree.is_leaf(node) && node.original.type === "folder") {
var path = tree.get_path(node, "/");
Shiny.setInputValue("path", path);
var interval = setInterval(function () {
if (Children !== null) {
clearInterval(interval);
for (var i = 0; i < Children.elem.length; i++) {
var isdir = Children.folder[i];
var newnode = tree.create_node(id, {
text: Children.elem[i],
type: isdir ? "folder" : "file",
children: false,
li_attr: isdir ? { class: "jstree-x" } : null
});
if (isdir) {
tree.disable_checkbox(newnode);
tree.disable_node(newnode);
}
}
Children = null;
setTimeout(function () {
tree.open_node(id);
}, 10);
}
}, 100);
}
});
});
EDIT 2
The new version of the package provides a Shiny module allowing to conveniently renders such a 'tree navigator' (or even several ones). This is the example given in the package:
library(shiny)
library(jsTreeR)
css <- HTML("
.flexcol {
display: flex;
flex-direction: column;
width: 100%;
margin: 0;
}
.stretch {
flex-grow: 1;
height: 1px;
}
.bottomright {
position: fixed;
bottom: 0;
right: 15px;
min-width: calc(50% - 15px);
}
")
ui <- fixedPage(
tags$head(
tags$style(css)
),
class = "flexcol",
br(),
fixedRow(
column(
width = 6,
treeNavigatorUI("explorer")
),
column(
width = 6,
tags$div(class = "stretch"),
tags$fieldset(
class = "bottomright",
tags$legend(
tags$h1("Selections:", style = "float: left;"),
downloadButton(
"dwnld",
class = "btn-primary btn-lg",
style = "float: right;",
icon = icon("save")
)
),
verbatimTextOutput("selections")
)
)
)
)
server <- function(input, output, session){
Paths <- treeNavigatorServer(
"explorer", rootFolder = getwd(),
search = list( # (search in the visited folders only)
show_only_matches = TRUE,
case_sensitive = TRUE,
search_leaves_only = TRUE
)
)
output[["selections"]] <- renderPrint({
cat(Paths(), sep = "\n")
})
}
shinyApp(ui, server)

How to copy a plot into the clipboard for pasting?

In running the below reproducible code, the user can select to view either the actual data or a plot of the data via a click of the radio button at the top of the rendered Shiny screen (as coded it defaults to data). At the bottom of the rendered screen you'll see a "Copy" button. By selecting "Data" and then "Copy", you can easily paste the data into XLS.
However, if the user instead selects to view the plot, I'd like the user to also be able to copy/paste the plot in the same manner. How can this be done?
I've tried inserting plotPNG(...) inside the capture.output(...) function (and various iterations thereof) in the below observeEvent(...), using conditionals triggered by a conditional if input$view == 'Plot', but with no luck yet.
library(shiny)
library(ggplot2)
ui <- fluidPage(
radioButtons("view",
label = "View data or plot",
choiceNames = c('Data','Plot'),
choiceValues = c('Data','Plot'),
selected = 'Data',
inline = TRUE
),
conditionalPanel("input.view == 'Data'",tableOutput("DF")),
conditionalPanel("input.view == 'Plot'",plotOutput("plotDF")),
actionButton("copy","Copy",style = "width:20%;")
)
server <- function(input, output, session) {
data <- data.frame(Period = c(1,2,3,4,5,6),Value = c(10,20,15,40,35,30))
output$DF <- renderTable(data)
output$plotDF <- renderPlot(ggplot(data, aes(Period,Value)) + geom_line())
observeEvent(
req(input$copy),
writeLines(
capture.output(
write.table(
x = data,
sep = "\t",
row.names = FALSE
)
),
"clipboard")
)
}
shinyApp(ui, server)
Tested on Edge.
library(shiny)
library(ggplot2)
js <- '
async function getImageBlobFromUrl(url) {
const fetchedImageData = await fetch(url);
const blob = await fetchedImageData.blob();
return blob;
}
$(document).ready(function () {
$("#copybtn").on("click", async () => {
const src = $("#plotDF>img").attr("src");
try {
const blob = await getImageBlobFromUrl(src);
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
alert("Image copied to clipboard!");
} catch (err) {
console.error(err.name, err.message);
alert("There was an error while copying image to clipboard :/");
}
});
});
'
ui <- fluidPage(
tags$head(
tags$script(HTML(js))
),
br(),
actionButton("copybtn", "Copy", icon = icon("copy"), class = "btn-primary"),
br(),
plotOutput("plotDF")
)
server <- function(input, output, session){
output[["plotDF"]] <- renderPlot({
ggplot(
iris, aes(x = Sepal.Length, y = Sepal.Width)
) + geom_point()
})
}
shinyApp(ui, server)
EDIT
Alerts are not nice. I suggest shinyToastify instead.
library(shiny)
library(shinyToastify)
library(ggplot2)
js <- '
async function getImageBlobFromUrl(url) {
const fetchedImageData = await fetch(url);
const blob = await fetchedImageData.blob();
return blob;
}
$(document).ready(function () {
$("#copybtn").on("click", async () => {
const src = $("#plotDF>img").attr("src");
try {
const blob = await getImageBlobFromUrl(src);
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
Shiny.setInputValue("success", true, {priority: "event"});
} catch (err) {
console.error(err.name, err.message);
Shiny.setInputValue("failure", true, {priority: "event"});
}
});
});
'
ui <- fluidPage(
tags$head(
tags$script(HTML(js))
),
useShinyToastify(),
br(),
actionButton("copybtn", "Copy", icon = icon("copy"), class = "btn-primary"),
br(),
plotOutput("plotDF")
)
server <- function(input, output, session){
output[["plotDF"]] <- renderPlot({
ggplot(
iris, aes(x = Sepal.Length, y = Sepal.Width)
) + geom_point()
})
observeEvent(input[["success"]], {
showToast(
session,
input,
text = tags$span(
style = "color: white; font-size: 20px;", "Image copied!"
),
type = "success",
position = "top-center",
autoClose = 3000,
pauseOnFocusLoss = FALSE,
draggable = FALSE,
style = list(
border = "4px solid crimson",
boxShadow = "rgba(0, 0, 0, 0.56) 0px 22px 30px 4px"
)
)
})
observeEvent(input[["failure"]], {
showToast(
session,
input,
text = tags$span(
style = "color: white; font-size: 20px;", "Failed to copy image!"
),
type = "error",
position = "top-center",
autoClose = 3000,
pauseOnFocusLoss = FALSE,
draggable = FALSE,
style = list(
border = "4px solid crimson",
boxShadow = "rgba(0, 0, 0, 0.56) 0px 22px 30px 4px"
)
)
})
}
shinyApp(ui, server)
You may try shinyscreenshot: You can further tweak it https://daattali.com/shiny/shinyscreenshot-demo/
Here is an example:
library(shiny)
library(ggplot2)
library(shinyscreenshot)
ui <- fluidPage(
radioButtons("view",
label = "View data or plot",
choiceNames = c('Data','Plot'),
choiceValues = c('Data','Plot'),
selected = 'Data',
inline = TRUE
),
div(
id = "takemyscreenshot",
conditionalPanel("input.view == 'Data'",tableOutput("DF")),
conditionalPanel("input.view == 'Plot'",plotOutput("plotDF")),
actionButton("go","Go",style = "width:20%;")
)
)
server <- function(input, output, session) {
observeEvent(input$go, {
screenshot(id = "takemyscreenshot")
})
data <- data.frame(Period = c(1,2,3,4,5,6),Value = c(10,20,15,40,35,30))
output$DF <- renderTable(data)
output$plotDF <- renderPlot(ggplot(data, aes(Period,Value)) + geom_line())
observeEvent(
req(input$copy),
writeLines(
capture.output(
write.table(
x = data,
sep = "\t",
row.names = FALSE
)
),
"clipboard")
)
}
shinyApp(ui, server)

If statement treats the condition as FALSE although the condition prints out as TRUE

I am creating a shiny app, which will show modal only when a user visits the app for the first time. After the first time, modal will not be shown. However, this does not work and modal is shown in every visit.
First I set a session ID as cookies and then tried to put modal inside if statement so that if the session ID is present and equal to what I set, it will not be executed. Although the statement in if condition is FALSE, the modal is still shown. In addition, if I write FALSE inside if condition manually, it works and doesn't show the modal. I used textOutput to show if the condition is TRUE or FALSE and it shows the opposite of how if statement is behaving.
I have also tried to set only input$jscookie != sessionid as condition, but it gives Error in if: argument is of length zero error although it prints FALSE when I use same statement in textOutput({input$jscookie != sessionid}).
Below is a reproducible example showing what I'm experiencing. Thank you in advance.
library(shiny)
library(shinyjs)
if (!dir.exists('www/')) {
dir.create('www')
}
download.file(
url = 'https://github.com/js-cookie/js-cookie/releases/download/v2.2.1/js.cookie-2.2.1.min.js',
destfile = 'www/js.cookie.js'
)
sessionid <- "OQGYIrpOvV3KnOpBSPgOhqGxz2dE5A9IpKhP6Dy2kd7xIQhLjwYzskn9mIhRAVHo29"
addResourcePath("js", "www")
jsCode <- '
shinyjs.getcookie = function(params) {
var cookie = Cookies.get("id");
if (typeof cookie !== "undefined") {
Shiny.onInputChange("jscookie", cookie);
} else {
var cookie = "";
Shiny.onInputChange("jscookie", cookie);
}
}
shinyjs.setcookie = function(params) {
Cookies.set("id", escape(params), { expires: 0.5 });
Shiny.onInputChange("jscookie", params);
}
shinyjs.rmcookie = function(params) {
Cookies.remove("id");
Shiny.onInputChange("jscookie", "");
}
'
ui <- fluidPage(
tags$head(
tags$script(src = "js/js.cookie.js")
),
useShinyjs(),
extendShinyjs(text = jsCode),
textOutput('id'),
verbatimTextOutput('sometext')
)
server <- function(input, output) {
observeEvent("", {
js$getcookie()
if (is.null(input$jscookie) || input$jscookie != sessionid) {
showModal(modalDialog(
"Click close button",
easyClose = TRUE,
footer = tagList(
actionButton(inputId = "close", label = "Close", icon = icon("close"))
)
))
}
})
observeEvent(input$close,{
removeModal()
js$setcookie(sessionid)
})
sometext<-reactive({
is.null(input$jscookie) || input$jscookie != sessionid
})
output$id<-renderText({
paste('session ID:', input$jscookie)
})
output$sometext<-renderText({
sometext()
})
}
shinyApp(ui = ui, server = server)
Ok, I've solved it.
Using input$jscookie as statement in observeEvent and putting js$getcookie() outside of observeEvent solved the problem.
library(shiny)
library(shinyjs)
if (!dir.exists('www/')) {
dir.create('www')
}
download.file(
url = 'https://github.com/js-cookie/js-cookie/releases/download/v2.2.1/js.cookie-2.2.1.min.js',
destfile = 'www/js.cookie.js'
)
sessionid <- "OQGYIrpOvV3KnOpBSPgOhqGxz2dE5A9IpKhP6Dy2kd7xIQhLjwYzskn9mIhRAVHo291"
addResourcePath("js", "www")
jsCode <- '
shinyjs.getcookie = function(params) {
var cookie = Cookies.get("id");
if (typeof cookie !== "undefined") {
Shiny.onInputChange("jscookie", cookie);
} else {
var cookie = "";
Shiny.onInputChange("jscookie", cookie);
}
}
shinyjs.setcookie = function(params) {
Cookies.set("id", escape(params), { expires: 0.5 });
Shiny.onInputChange("jscookie", params);
}
shinyjs.rmcookie = function(params) {
Cookies.remove("id");
Shiny.onInputChange("jscookie", "");
}
'
ui <- fluidPage(
tags$head(
tags$script(src = "js/js.cookie.js")
),
useShinyjs(),
extendShinyjs(text = jsCode),
textOutput('id'),
verbatimTextOutput('sometext')
)
server <- function(input, output) {
js$getcookie()
observeEvent(input$jscookie, {
if (is.null(input$jscookie) || input$jscookie != sessionid) {
showModal(modalDialog(
"Click close button",
easyClose = TRUE,
footer = tagList(
actionButton(inputId = "close", label = "Close", icon = icon("close"))
)
))
}
})
observeEvent(input$close,{
removeModal()
js$setcookie(sessionid)
})
sometext<-reactive({
is.null(input$jscookie) || input$jscookie != sessionid
})
output$id<-renderText({
paste('session ID:', input$jscookie)
})
output$sometext<-renderText({
sometext()
})
}
shinyApp(ui = ui, server = server)

R Shiny reset input values for mychooser function of the Shiny gallery

I am still learning about Shiny and developing my own application.
The following widget allows to select columns however the user wants (with live example):
https://shiny.rstudio.com/gallery/custom-input-control.html
UI.R
source("chooser.R")
fluidPage(
chooserInput("mychooser", "Available frobs", "Selected frobs",
row.names(USArrests), c(), size = 10, multiple = TRUE
),
verbatimTextOutput("selection")
)
server.R
function(input, output, session) {
output$selection <- renderPrint(
input$mychooser
)
}
chooser.R
chooserInput <- function(inputId, leftLabel, rightLabel, leftChoices, rightChoices,
size = 5, multiple = FALSE) {
leftChoices <- lapply(leftChoices, tags$option)
rightChoices <- lapply(rightChoices, tags$option)
if (multiple)
multiple <- "multiple"
else
multiple <- NULL
tagList(
singleton(tags$head(
tags$script(src="chooser-binding.js"),
tags$style(type="text/css",
HTML(".chooser-container { display: inline-block; }")
)
)),
div(id=inputId, class="chooser",
div(class="chooser-container chooser-left-container",
tags$select(class="left", size=size, multiple=multiple, leftChoices)
),
div(class="chooser-container chooser-center-container",
icon("arrow-circle-o-right", "right-arrow fa-3x"),
tags$br(),
icon("arrow-circle-o-left", "left-arrow fa-3x")
),
div(class="chooser-container chooser-right-container",
tags$select(class="right", size=size, multiple=multiple, rightChoices)
)
)
)
}
registerInputHandler("shinyjsexamples.chooser", function(data, ...) {
if (is.null(data))
NULL
else
list(left=as.character(data$left), right=as.character(data$right))
}, force = TRUE)
www/chooser-binding.js
(function() {
function updateChooser(chooser) {
chooser = $(chooser);
var left = chooser.find("select.left");
var right = chooser.find("select.right");
var leftArrow = chooser.find(".left-arrow");
var rightArrow = chooser.find(".right-arrow");
var canMoveTo = (left.val() || []).length > 0;
var canMoveFrom = (right.val() || []).length > 0;
leftArrow.toggleClass("muted", !canMoveFrom);
rightArrow.toggleClass("muted", !canMoveTo);
}
function move(chooser, source, dest) {
chooser = $(chooser);
var selected = chooser.find(source).children("option:selected");
var dest = chooser.find(dest);
dest.children("option:selected").each(function(i, e) {e.selected = false;});
dest.append(selected);
updateChooser(chooser);
chooser.trigger("change");
}
$(document).on("change", ".chooser select", function() {
updateChooser($(this).parents(".chooser"));
});
$(document).on("click", ".chooser .right-arrow", function() {
move($(this).parents(".chooser"), ".left", ".right");
});
$(document).on("click", ".chooser .left-arrow", function() {
move($(this).parents(".chooser"), ".right", ".left");
});
$(document).on("dblclick", ".chooser select.left", function() {
move($(this).parents(".chooser"), ".left", ".right");
});
$(document).on("dblclick", ".chooser select.right", function() {
move($(this).parents(".chooser"), ".right", ".left");
});
var binding = new Shiny.InputBinding();
binding.find = function(scope) {
return $(scope).find(".chooser");
};
binding.initialize = function(el) {
updateChooser(el);
};
binding.getValue = function(el) {
return {
left: $.makeArray($(el).find("select.left option").map(function(i, e) { return e.value; })),
right: $.makeArray($(el).find("select.right option").map(function(i, e) { return e.value; }))
}
};
binding.setValue = function(el, value) {
// TODO: implement
};
binding.subscribe = function(el, callback) {
$(el).on("change.chooserBinding", function(e) {
callback();
});
};
binding.unsubscribe = function(el) {
$(el).off(".chooserBinding");
};
binding.getType = function() {
return "shinyjsexamples.chooser";
};
Shiny.inputBindings.register(binding, "shinyjsexamples.chooser");
})();
Once the columns have been scrambled, we can't go back to the original order except reloading the application. I'd like to have an actionButton that resets all the columns of mychooser to their default values.
What I tried so far
UI.R
source("chooser.R")
fluidPage(
chooserInput("mychooser", "Available frobs", "Selected frobs",
row.names(USArrests), c(), size = 10, multiple = TRUE
),
actionButton(inputId = "resetcols", label = "Reset"),
verbatimTextOutput("selection")
)
server.R
function(input, output, session) {
colvalues <- row.names(USArrests)
output$selection <- renderPrint(
input$mychooser
)
eventReactive(input$resetcols, {
output$mychooser <- row.names(USArrests)
})
}
However, there's nothing happened and I don't know what can I do.
Thanks in advance
Here is the way.
Add this JS code in chooser-binding.js, e.g. after binding.setValue:
binding.receiveMessage = function (el, data) {
$(".chooser select.left").empty();
$(".chooser select.right").empty();
if(data.left !== null){
for(var i = 0; i < data.left.length; ++i){
$(".chooser select.left")
.append($("<option>" + data.left[i] + "</option>"));
}
}
if(data.right !== null){
for(var i = 0; i < data.right.length; ++i){
$(".chooser select.right")
.append($("<option>" + data.right[i] + "</option>"));
}
}
var chooser = $(el);
updateChooser(chooser);
chooser.trigger("change");
};
Define the updater for chooserInput:
updateChooserInput <- function(session, inputId, left, right){
session$sendInputMessage(inputId, list(right = right, left = left))
}
Now, here is an example:
# ui ####
ui <- fluidPage(
br(),
chooserInput("mychooser", "Available frobs", "Selected frobs",
row.names(USArrests), c(), size = 10, multiple = TRUE
),
verbatimTextOutput("selection"),
br(),
actionButton("update", "Update"),
actionButton("reset", "Reset")
)
# server ####
server <- function(input, output, session) {
output$selection <- renderPrint(
input$mychooser
)
observeEvent(input$update, {
updateChooserInput(session, "mychooser",
left = c("aaa", "bbb", "ccc"),
right = c("xxx", "yyy", "zzz"))
})
observeEvent(input$reset, {
updateChooserInput(session, "mychooser",
left = row.names(USArrests),
right = c())
})
}
shinyApp(ui, server)

Add a search box to custom input control in shiny

My goal is to add a search box on top of the custom input control in shiny. I would like when a user searches Hampshire for example, the selection to pick New Hampshire which is not currently possible as it searches just by the first letter.
server.R
shinyServer(function(input, output, session) {
output$main <- renderUI({
source("chooser.R")
chooserInput("mychooser","Available frobs","Selected frobs",
row.names(USArrests),c(),size=20,multiple=TRUE)})
})
ui.R
source("chooser.R")
shinyUI(fluidPage(
uiOutput("main")
))
chooser.R
chooserInput <- function(inputId, leftLabel, rightLabel, leftChoices, rightChoices,
size = 5, multiple = FALSE) {
leftChoices <- lapply(leftChoices, tags$option)
rightChoices <- lapply(rightChoices, tags$option)
if (multiple)
multiple <- "multiple"
else
multiple <- NULL
tagList(
singleton(tags$head(
tags$script(src="chooser-binding.js"),
tags$style(type="text/css",
HTML(".chooser-container { display: inline-block; }")
)
)),
div(id=inputId, class="chooser",
div(class="chooser-container chooser-left-container",
tags$select(class="left", size=size, multiple=multiple, leftChoices)
),
div(class="chooser-container chooser-center-container",
icon("arrow-circle-o-right", "right-arrow fa-3x"),
tags$br(),
icon("arrow-circle-o-left", "left-arrow fa-3x")
),
div(class="chooser-container chooser-right-container",
tags$select(class="right", size=size, multiple=multiple, rightChoices)
)
)
)
}
registerInputHandler("shinyjsexamples.chooser", function(data, ...) {
if (is.null(data))
NULL
else
list(left=as.character(data$left), right=as.character(data$right))
}, force = TRUE)
chooser-binding.js (in www folder)
(function() {
function updateChooser(chooser) {
chooser = $(chooser);
var left = chooser.find("select.left");
var right = chooser.find("select.right");
var leftArrow = chooser.find(".left-arrow");
var rightArrow = chooser.find(".right-arrow");
var canMoveTo = (left.val() || []).length > 0;
var canMoveFrom = (right.val() || []).length > 0;
leftArrow.toggleClass("muted", !canMoveFrom);
rightArrow.toggleClass("muted", !canMoveTo);
}
function move(chooser, source, dest) {
chooser = $(chooser);
var selected = chooser.find(source).children("option:selected");
var dest = chooser.find(dest);
dest.children("option:selected").each(function(i, e) {e.selected = false;});
dest.append(selected);
updateChooser(chooser);
chooser.trigger("change");
}
$(document).on("change", ".chooser select", function() {
updateChooser($(this).parents(".chooser"));
});
$(document).on("click", ".chooser .right-arrow", function() {
move($(this).parents(".chooser"), ".left", ".right");
});
$(document).on("click", ".chooser .left-arrow", function() {
move($(this).parents(".chooser"), ".right", ".left");
});
$(document).on("dblclick", ".chooser select.left", function() {
move($(this).parents(".chooser"), ".left", ".right");
});
$(document).on("dblclick", ".chooser select.right", function() {
move($(this).parents(".chooser"), ".right", ".left");
});
var binding = new Shiny.InputBinding();
binding.find = function(scope) {
return $(scope).find(".chooser");
};
binding.initialize = function(el) {
updateChooser(el);
};
binding.getValue = function(el) {
return {
left: $.makeArray($(el).find("select.left option").map(function(i, e) { return e.value; })),
right: $.makeArray($(el).find("select.right option").map(function(i, e) { return e.value; }))
}
};
binding.setValue = function(el, value) {
// TODO: implement
};
binding.subscribe = function(el, callback) {
$(el).on("change.chooserBinding", function(e) {
callback();
});
};
binding.unsubscribe = function(el) {
$(el).off(".chooserBinding");
};
binding.getType = function() {
return "shinyjsexamples.chooser";
};
Shiny.inputBindings.register(binding, "shinyjsexamples.chooser");
})();
Cool widget (or whatever the terminology is). This question has actually been answered here so make sure to vote on the persons answer if it helps you.
Here's a super simple implementations of it (could be better):
chooser.R
chooserInput <- function(inputId, leftLabel, rightLabel, leftChoices, rightChoices,
size = 5, multiple = FALSE) {
leftChoices <- lapply(leftChoices, tags$option)
rightChoices <- lapply(rightChoices, tags$option)
if (multiple)
multiple <- "multiple"
else
multiple <- NULL
tagList(
singleton(tags$head(
tags$script(src="chooser-binding.js"),
tags$style(type="text/css",
HTML(".chooser-container { display: inline-block; }")
)
)),
div(id=inputId, class="chooser",style="",
div(
div(style="min-width:100px;",
tags$input(type="text",class="chooser-input-search",style="width:100px;")
)
),
div(style="display:table",
div(style="min-width:100px; display:table-cell;",
div(class="chooser-container chooser-left-container",
style="width:100%;",
tags$select(class="left", size=size, multiple=multiple, leftChoices,style="width:100%;min-width:100px")
)
),
div(style="min-width:50px; display:table-cell;vertical-align: middle;",
div(class="chooser-container chooser-center-container",
style="padding:10px;",
icon("arrow-circle-o-right", "right-arrow fa-3x"),
tags$br(),
icon("arrow-circle-o-left", "left-arrow fa-3x")
)
),
div(style="min-width:100px; display:table-cell;",
div(class="chooser-container chooser-right-container", style="width:100%;",
tags$select(class="right", size=size, multiple=multiple, rightChoices,style="width:100%;")
)
)
)
)
)
}
registerInputHandler("shinyjsexamples.chooser", function(data, ...) {
if (is.null(data))
NULL
else
list(left=as.character(data$left), right=as.character(data$right))
}, force = TRUE)
chooser-bindings.js
(function() {
var options = [];
jQuery.fn.filterByText = function(textbox, selectSingleMatch) {
return this.each(function() {
var select = this;
options = [];
$(select).find('option').each(function() {
options.push({value: $(this).val(), text: $(this).text()});
});
$(select).data('options', options);
$(textbox).bind('change keyup', function() {
options = $(select).empty().scrollTop(0).data('options');
var search = $.trim($(this).val());
var regex = new RegExp(search,'gi');
$.each(options, function(i) {
var option = options[i];
if(option.text.match(regex) !== null) {
$(select).append(
$('<option>').text(option.text).val(option.value)
);
}
});
if (selectSingleMatch === true &&
$(select).children().length === 1) {
$(select).children().get(0).selected = true;
}
});
});
};
function updateChooser(chooser) {
chooser = $(chooser);
var left = chooser.find("select.left");
var right = chooser.find("select.right");
var leftArrow = chooser.find(".left-arrow");
var rightArrow = chooser.find(".right-arrow");
var canMoveTo = (left.val() || []).length > 0;
var canMoveFrom = (right.val() || []).length > 0;
leftArrow.toggleClass("muted", !canMoveFrom);
rightArrow.toggleClass("muted", !canMoveTo);
}
function move(chooser, source, dest) {
chooser = $(chooser);
var selected = chooser.find(source).children("option:selected");
var dest = chooser.find(dest);
dest.children("option:selected").each(function(i, e) {e.selected = false;});
dest.append(selected);
updateChooser(chooser);
chooser.trigger("change");
}
$(".chooser").change(function(){
});
$(document).on("change", ".chooser select", function() {
updateChooser($(this).parents(".chooser"));
});
$(document).on("click", ".chooser .right-arrow", function() {
move($(this).parents(".chooser"), ".left", ".right");
});
$(document).on("click", ".chooser .left-arrow", function() {
move($(this).parents(".chooser"), ".right", ".left");
});
$(document).on("dblclick", ".chooser select.left", function() {
move($(this).parents(".chooser"), ".left", ".right");
});
$(document).on("dblclick", ".chooser select.right", function() {
move($(this).parents(".chooser"), ".right", ".left");
});
var binding = new Shiny.InputBinding();
binding.find = function(scope) {
return $(scope).find(".chooser");
};
binding.initialize = function(el) {
updateChooser(el);
$(function() {
$('.left').filterByText($('.chooser-input-search'), true);
});
};
binding.getValue = function(el) {
return {
left: $.makeArray($(el).find("select.left option").map(function(i, e) { return e.value; })),
right: $.makeArray($(el).find("select.right option").map(function(i, e) { return e.value; }))
}
};
binding.setValue = function(el, value) {
// TODO: implement
};
binding.subscribe = function(el, callback) {
$(el).on("change.chooserBinding", function(e) {
callback();
});
};
binding.unsubscribe = function(el) {
$(el).off(".chooserBinding");
};
binding.getType = function() {
return "shinyjsexamples.chooser";
};
Shiny.inputBindings.register(binding, "shinyjsexamples.chooser");
})();
As you can see this is pretty much a shameful copy and paste.

Resources