我创建了许多输入(参数)的Shiny应用程序。我们的用户希望返回相同的输入值。
我已经检查了这个示例(http://shiny.rstudio.com/articles/client-data.html),它显示通过会话$ clientData $ url_search获取url,但是无法从左侧的sidebarPanel输入生成url。例如:
http://localhost:8100/?obs=10
如何生成可以在Shiny中恢复相同值的URL?由于有很多输入,因此短期应该是最好的。
如果我的问题不明确,请告诉我。
感谢您的任何建议。
答案 0 :(得分:14)
为了简单起见,您不必在server.R
中编写任何代码。通过编写一些javascript代码,可以很好地解析URL查询字符串(例如?obs=10
)并设置相应的输入。
下面我将举一个简单的例子,您可以看到如何动态设置Shiny的任何内置输入控件的值。
shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
# wrap input controls into a container so that we can use binding.find()
# function to quickly locate the input controls.
tags$div(id="input_container",
textInput("username", h6("Username:")),
numericInput("age", h6("Age:"),
min=1, max=99, value=20, step=1),
selectInput("sex", h6("Sex:"), choices=c("Male", "Female")),
# load Javascript snippet to parse the query string.
singleton(tags$script(type="text/javascript",
src="js/parse_input.js"))
)
),
mainPanel(
verbatimTextOutput("log")
)
)
)
)
# does nothing but echoes back the user's input values
shinyServer(function(input, output) {
output$log <- renderPrint({
paste("Username: ", input$username, "; Age: ", input$age,
"; Sex: ", input$sex, sep="")
})
})
最后,您需要在Shiny项目目录下创建文件夹www/js
,并将此parse_input.js
文件放在js
文件夹中。
$(document).ready(function() {
if (window.location.search) {
var input_params = {};
/* process query string, e.g. ?obs=10&foo=bar */
var params = $.map(
window.location.search.match(/[\&\?]\w+=[^\&]+/g),
function(p, i) {
var kv = p.substring(1).split("=");
# NOTE: might have issue to parse some special characters here?
input_params[kv[0]] = decodeURIComponent(kv[1]);
}
);
/* Shiny.inputBindings.getBindings() return the InputBinding instances
for every (native) input type that Shiny supports (selectInput, textInput,
actionButton etc.) */
$.each(Shiny.inputBindings.getBindings(), function(i, b) {
/* find all inputs within a specific input type */
var inputs = b.binding.find('#input_container');
$.each(inputs, function(j, inp) {
/* check if the input's id matches the key specified in the query
string */
var inp_val = input_params[$(inp).attr("id")];
if (inp_val != undefined) {
b.binding.setValue(inp, inp_val);
}
});
});
}
});
然后,您可以使用http://localhost:7691/?sex=Female&age=44&username=Jane
等网址访问该网站。
您应该在主面板上看到文本变为:
[1] "Username: Jane; Age: 44; Sex: Female"
shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
# wrap input controls into a container
tags$div(id="input_container",
textInput("username", h6("Username:")),
numericInput("age", h6("Age:"),
min=1, max=99, value=20, step=1),
selectInput("sex", h6("Sex:"), choices=c("Male", "Female")),
singleton(tags$script(type="text/javascript",
src="js/parse_input.js"))
),
tags$button(type="button", id="save_options",
h6("Save current options")),
tags$input(type="text", style="display:none;", value="{}",
id="inputs_snapshot")
),
mainPanel(
verbatimTextOutput("log"),
verbatimTextOutput("gen_url")
)
)
)
)
# user.saved.snapshots <- list(
# list(sex="Male", age=32, username="Jason"),
# list(sex="Male", age=16, username="Eric"),
# list(sex="Female", age=46, username="Peggy")
# )
#
# save(user.saved.snapshots, file="snapshots.Rdata")
# ^^ Run above code **ONCE** to initiate a dummy data file, storing some possible options.
load("snapshots.Rdata")
renderRestoration <- function(expr, env=parent.frame(), quoted=F) {
func <- exprToFunction(expr)
function() {
func()
# return the selected snapshot to the client side
# Shiny will automatically wrap it into JSOn
}
}
shinyServer(function(input, output, session) {
output$log <- renderPrint({
paste("Username: ", input$username, "; Age: ", input$age,
"; Sex: ", input$sex, "\n\n", "User saved sets: ", str(user.saved.snapshots), sep="")
})
observe({
if (!is.null(input$inputs_snapshot) && length(input$inputs_snapshot) > 0) {
print(input$inputs_snapshot)
user.saved.snapshots[[length(user.saved.snapshots) + 1]] <<- input$inputs_snapshot
save(user.saved.snapshots, file="snapshots.Rdata")
}
})
output$input_container <- renderRestoration({
query <- parseQueryString(session$clientData$url_search)
if (is.null(query$snapshot)) return (list())
sid <- as.numeric(query$snapshot)
if (sid <= length(user.saved.snapshots)) {
user.saved.snapshots[[sid]]
}
})
output$gen_url <- renderPrint({
if (length(input$inputs_snapshot) > 0) {
paste("The current input snapshot is created, and can be restored by visiting: \n",
session$clientData$url_protocol, "://",
session$clientData$url_hostname, ":",
session$clientData$url_port,
session$clientData$url_pathname, "?snapshot=", length(user.saved.snapshots),
sep=""
)
}
})
})
$(document).ready(function() {
if (window.location.search) {
/* METHOD 1: restore from a explicit URL specifying all inputs */
var input_params = {};
/* process query string, e.g. ?obs=10&foo=bar */
var params = $.map(
window.location.search.match(/[\&\?]\w+=[^\&]+/g),
function(p, i) {
var kv = p.substring(1).split("=");
input_params[kv[0]] = decodeURIComponent(kv[1]);
}
);
// you can uncomment this if you want to restore inputs from an
// explicit options specified in the URL in format:
// input_id=value
//restore_snapshot("#input_container", input_params);
}
var restore_snapshot = function(el, input_params) {
/* Shiny.inputBindings.getBindings() return the InputBinding instances
for every (native) input type that Shiny supports (selectInput, textInput,
actionButton etc.) */
$.each(Shiny.inputBindings.getBindings(), function(i, b) {
/* find all inputs within a specific input type */
var inputs = b.binding.find(el);
$.each(inputs, function(j, inp) {
/* check if the input's id matches the key specified in the query
string */
var inp_val = input_params[$(inp).attr("id")];
if (inp_val != undefined) {
b.binding.setValue(inp, inp_val);
}
});
});
}
$("#save_options").on('click', function() {
/* dump all inputs within input container */
var input_params = {}
$.each(Shiny.inputBindings.getBindings(), function(i, b) {
/* find all inputs within a specific input type */
var inputs = b.binding.find('#input_container');
$.each(inputs, function(j, inp) {
/* check if the input's id matches the key specified in the query
string */
var inp_id = $(inp).attr("id");
if (inp_id) {
input_params[inp_id] = b.binding.getValue(inp);
}
});
});
console.log(input_params);
$("#inputs_snapshot").val(JSON.stringify(input_params))
.trigger("change");
});
/* ------------ Shiny Bindings -------------- */
/* First, an input binding monitor change of a hidden input,
* whose value will be changed once the user clicks the
* "save current options" button.
*/
var snapshotBinding = new Shiny.InputBinding();
$.extend(snapshotBinding, {
find: function(scope) {
return $(scope).find("#inputs_snapshot");
},
getValue: function(el) {
return JSON.parse($(el).val());
},
subscribe: function(el, callback) {
$(el).on("change.snapshot", function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off(".snapshot");
}
});
Shiny.inputBindings.register(snapshotBinding);
var restoreBinding = new Shiny.OutputBinding();
$.extend(restoreBinding, {
find: function(scope) {
return $(scope).find("#input_container");
},
renderValue: function(el, data) {
// very rudimentary sanity check
if ($.isPlainObject(data) && data.hasOwnProperty('username')) {
restore_snapshot(el, data);
alert("Snapshot restored!");
}
}
});
Shiny.outputBindings.register(restoreBinding, 'inputs.Restore');
});
一个简短的解释:
<input>
标记。这允许我们将输入的当前快照发送到服务器。user.saved.snapshots
变量,并将其保存到磁盘文件中。 ?snapshot=[number]
可见,服务器将仅向客户端发送有效数据。 input$inputs_snapshot
列表对象创建显式恢复URL(例如 ?username=Eric&age=44&sex=Male
),因为您可以从那里访问所有输入值。我们的javascript也提供了这个功能。有许多细节需要打磨。
您可以考虑使用RSQLite
包将这些配置文件保存到SQLite数据库。
但上面的演示应该是一个很好的概念证明。
答案 1 :(得分:3)
对于基于R的解决方案,解决将Shiny应用程序的小部件的当前状态编码为URL查询字符串以及从该URL恢复用户输入值的问题,请参阅shinyURL包。它还具有方便的复制到剪贴板按钮,以及与TinyURL Web服务的接口,用于缩短URL。
该软件包非常易于安装和使用。它可以从GitHub获得:
devtools::install_github("aoles/shinyURL")
要在您的应用中启用shinyURL,请按以下3个步骤操作:
在 server.R 和 ui.R 中加载包。
library("shinyURL")
在 server.R 中的闪亮服务器函数内添加对shinyURL.server(session)
的调用,其中session
是传递给服务器函数的参数。
将shinyURL.ui()
窗口小部件添加到 ui.R 。
答案 2 :(得分:0)
基于@ xin-yin建议,我添加了几行代码,以便在观察服务器中的功能时允许保存当前选项.R(基于来自https://gist.github.com/alexbbrown/6e77383b48a044191771的想法)。所有代码都粘贴在这里以防其他人需要它们。
ui.R
Same as @xin-yin answer
server.R
# user_saved_snapshots <- list(
# list(sex='Male', age=32, username='Jason'),
# list(sex='Male', age=16, username='Eric'),
# list(sex='Female', age=46, username='Peggy')
# )
#
# save(user_saved_snapshots, file='snapshots.Rdata')
# ^^ Run above code **ONCE** to initiate a dummy data file, storing some possible options.
user_saved_snapshots <- list()
if (file.exists('snapshots.Rdata'))
{
load('snapshots.Rdata')
}
renderRestoration <- function(expr, env = parent.frame(), quoted = F)
{
func <- exprToFunction(expr)
function()
{
func()
# return the selected snapshot to the client side
# Shiny will automatically wrap it into JSOn
}
}
shinyServer(function(input, output, session)
{
output$log <- renderPrint({
paste('Username: ', input$username, '; Age: ', input$age,
'; Sex: ', input$sex, '\n\n', 'User saved sets: ',
str(user_saved_snapshots), sep = '')
})
firstTime <- TRUE
observe({
age <- input$age
if (firstTime & nchar(session$clientData$url_search) > 0)
{
firstTime <<- FALSE
} else
{
updateTextInput(session, "username",
value = paste('AAAAA', age, sep = ': '))
}
})
observe({
print(input$inputs_snapshot)
print(session$clientData$url_search)
# if (nchar(session$clientData$url_search))
# {
if (!is.null(input$inputs_snapshot) && length(input$inputs_snapshot) > 0) {
# print(input$inputs_snapshot)
user_saved_snapshots[[length(user_saved_snapshots) + 1]] <<- input$inputs_snapshot
save(user_saved_snapshots, file='snapshots.Rdata')
}
# } else
# {
# updateNumericInput(session, 'age', value = 100)
# }
})
output$input_container <- renderRestoration({
query <- parseQueryString(session$clientData$url_search)
if (is.null(query$snapshot)) return (list())
sid <- as.numeric(query$snapshot)
if (sid <= length(user_saved_snapshots))
{
user_saved_snapshots[[sid]]
}
})
output$gen_url <- renderPrint({
if (length(input$inputs_snapshot) > 0)
{
url <- paste0(session$clientData$url_protocol, '//',
session$clientData$url_hostname, ':',
session$clientData$url_port,
session$clientData$url_pathname, '?snapshot=',
length(user_saved_snapshots))
tags$div(tags$p('The current input snapshot is created, and can be restored by visiting:'),
tags$a(url, href = url))
}
})
})
WWW / JS / parse_input.js
Same as @xin-yin answer
答案 3 :(得分:0)
建立daattali(Shiny saving URL state subpages and tabs),这需要任意数量的输入,并为几种不同类型的输入分配值:
ui.R:
library(shiny)
shinyUI(fluidPage(
textInput("symbol", "Symbol Entry", ""),
dateInput("date_start", h4("Start Date"), value = "2005-01-01" ,startview = "year"),
selectInput("period_select", label = h4("Frequency of Updates"),
c("Monthly" = 1,
"Quarterly" = 2,
"Weekly" = 3,
"Daily" = 4)),
sliderInput("smaLen", label = "SMA Len",min = 1, max = 200, value = 115),br(),
checkboxInput("usema", "Use MA", FALSE)
))
server.R:
shinyServer(function(input, output,session) {
observe({
query <- parseQueryString(session$clientData$url_search)
for (i in 1:(length(reactiveValuesToList(input)))) {
nameval = names(reactiveValuesToList(input)[i])
valuetoupdate = query[[nameval]]
if (!is.null(query[[nameval]])) {
if (is.na(as.numeric(valuetoupdate))) {
updateTextInput(session, nameval, value = valuetoupdate)
}
else {
updateTextInput(session, nameval, value = as.numeric(valuetoupdate))
}
}
}
})
})
要测试的示例网址:127.0.0.1:5767/?symbol=BBB,AAA,CCC,DDD&date_start=2005-01-02&period_select=2&smaLen=153&usema=1