├── .travis.yml ├── DESCRIPTION ├── NAMESPACE ├── R ├── deprecated.R ├── server.R ├── shinyURL.R ├── ui.R └── utils.R ├── README.md ├── inst ├── examples │ ├── dynamicUI │ │ └── app.R │ ├── qrcode │ │ └── qrcode.Rmd │ ├── showcase │ │ ├── app.R │ │ ├── description.md │ │ └── www │ │ │ └── style.css │ ├── tabsets │ │ ├── server.R │ │ └── ui.R │ └── widgets │ │ ├── server.R │ │ └── ui.R └── zeroclipboard │ ├── ZeroClipboard.min.js │ └── ZeroClipboard.swf └── man ├── shinyURL-deprecated.Rd └── shinyURL.Rd /.travis.yml: -------------------------------------------------------------------------------- 1 | # Sample .travis.yml for R projects 2 | 3 | language: r 4 | warnings_are_errors: true 5 | sudo: required 6 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: shinyURL 2 | Type: Package 3 | Title: Save and restore the state of Shiny apps 4 | Version: 0.0.35 5 | Encoding: UTF-8 6 | Author: Andrzej Oleś 7 | Maintainer: Andrzej Oleś 8 | Description: Save and restore the view state of a Shiny app by encoding the 9 | values of user inputs and active tab panels in the app's URL query string. 10 | Imports: 11 | methods, 12 | RCurl, 13 | shiny (>= 0.13.0), 14 | utils 15 | Suggests: markdown, qrcode, rmarkdown 16 | License: Artistic-2.0 17 | RoxygenNote: 5.0.1 18 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(shinyURL.Server) 4 | export(shinyURL.UI) 5 | export(shinyURL.server) 6 | export(shinyURL.ui) 7 | importFrom(RCurl,getURL) 8 | importFrom(methods,as) 9 | importFrom(shiny,actionButton) 10 | importFrom(shiny,addResourcePath) 11 | importFrom(shiny,div) 12 | importFrom(shiny,eventReactive) 13 | importFrom(shiny,getDefaultReactiveDomain) 14 | importFrom(shiny,icon) 15 | importFrom(shiny,includeScript) 16 | importFrom(shiny,invalidateLater) 17 | importFrom(shiny,isolate) 18 | importFrom(shiny,observe) 19 | importFrom(shiny,observeEvent) 20 | importFrom(shiny,parseQueryString) 21 | importFrom(shiny,reactive) 22 | importFrom(shiny,reactiveValuesToList) 23 | importFrom(shiny,tagList) 24 | importFrom(shiny,tags) 25 | importFrom(shiny,updateTextInput) 26 | importFrom(shiny,validateCssUnit) 27 | importFrom(stats,setNames) 28 | importFrom(utils,URLencode) 29 | importFrom(utils,flush.console) 30 | -------------------------------------------------------------------------------- /R/deprecated.R: -------------------------------------------------------------------------------- 1 | #' Deprecated functions in package \sQuote{shinyURL} 2 | #' 3 | #' These functions are provided for compatibility with older versions of 4 | #' \sQuote{shinyURL} only, and will be defunct at the next release. 5 | #' 6 | #' @details The following functions are deprecated and will be made defunct; use 7 | #' the replacement indicated below. \itemize{ \item{shinyURL.Server: 8 | #' \code{\link{shinyURL.server}}} \item{shinyURL.UI: 9 | #' \code{\link{shinyURL.ui}}} } 10 | #' @param ... Arguments passed to the new methods 11 | #' @rdname shinyURL-deprecated 12 | #' @export 13 | shinyURL.Server = function(...) { 14 | .Deprecated("shinyURL.server") 15 | shinyURL.server(...) 16 | } 17 | 18 | #' @rdname shinyURL-deprecated 19 | #' @export 20 | shinyURL.UI = function(...) { 21 | .Deprecated("shinyURL.ui") 22 | shinyURL.ui(...) 23 | } 24 | 25 | .invalidateOnInit = function(session, self) { 26 | observe({ 27 | invalidateLater(0, session) 28 | self$suspend() 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /R/server.R: -------------------------------------------------------------------------------- 1 | #' @details The \code{shinyURL.server} method contains server logic for encoding 2 | #' and restoring the widgets' values. It is called from inside the app's 3 | #' server script, and can take the \code{session} objects as argument. 4 | #' 5 | #' The argument \code{options} can contain a named list of options. These are 6 | #' set by a call to \code{\link[base]{options}} as \sQuote{shinyURL.name}. See below for a list of available options. 7 | #' @section ShinyURL options: 8 | #' \describe{ 9 | #' \item{\code{debug = TRUE}}{Print debug messages to the console} 10 | #' } 11 | #' @param session Typically the same as the optional parameter passed into the 12 | #' Shiny server function as an argument; if missing defaults to 13 | #' \code{getDefaultReactiveDomain()} 14 | #' @param options Named list of options 15 | #' @return \code{shinyURL.server} returns a reactive expression evaluating to 16 | #' the app's URL. 17 | #' @rdname shinyURL 18 | #' @export 19 | shinyURL.server = function(session, options) { 20 | if (missing(session)) 21 | session = getDefaultReactiveDomain() 22 | 23 | if (!missing(options)) 24 | options(setNames(options, paste("shinyURL", names(options), sep="."))) 25 | 26 | debugMsg("ShinyURL initializes") 27 | 28 | ## initialize from query string 29 | init = .initFromURL(session, init) 30 | 31 | ## encode current app's state 32 | url = .encodeURL(session, inputId) 33 | 34 | ## use TinyURL for shortening the URL 35 | .queryTinyURL(session) 36 | 37 | ## Initial invalidation needed to execute scheduled input updates when the 38 | ## browser is refreshed switched off because it interferes with dynamic UIs 39 | ## invalidate = .invalidateOnInit(session, invalidate) 40 | 41 | url 42 | } 43 | 44 | 45 | .initFromURL = function(session, self) { 46 | queryValues <- isolate(parseQueryString(session$clientData$url_search, nested=TRUE)) 47 | observe({ 48 | debugMsg(".initFromURL") 49 | queryValuesCopy = queryValues 50 | 51 | ## iterate through available inputs as long as there are any uninitialized 52 | ## values in queryValues the expression below depends on inputs which is 53 | ## neccassary to restore dynamic UIs 54 | inputValues = reactiveValuesToList(session$input, all.names=FALSE) 55 | updateValues = intersect(names(inputValues), names(queryValues)) 56 | queryIds = match(updateValues, names(queryValues)) 57 | inputIds = match(updateValues, names(inputValues)) 58 | 59 | if ( length(queryIds) > 0 ) queryValues <<- queryValues[-queryIds] 60 | 61 | ## schedule the update only after all input messages have been sent out (see 62 | ## the 'flushOutput' function in shiny.R). This is to avoid potential 63 | ## overwriting by some update events from user code 64 | #session$onFlushed(function() { 65 | .initInputs(session, queryValuesCopy[queryIds], inputValues[inputIds]) 66 | #}) 67 | 68 | ## suspend if nothing to do 69 | if ( length(queryValues) == 0L ) 70 | self$suspend() 71 | 72 | }, priority = -99) 73 | } 74 | 75 | 76 | .initInputs = function(session, queryValues, inputValues) { 77 | 78 | for (i in seq_along(queryValues)) { 79 | q = queryValues[[i]] 80 | 81 | q = if (is.list(q)) { 82 | ## checkbox group or multiple select 83 | unlist(q, use.names=FALSE) 84 | } 85 | else { 86 | ## decode range vectors (sliders and dates) 87 | if (length(inputValues[[i]])>1L) 88 | q = unlist(strsplit(q, ",")) 89 | ## use information about the class of the inputs when initializing them 90 | cl = class(inputValues[[i]]) 91 | ## promote integer to numeric because numericInputs can contain either 92 | if (cl=="integer") 93 | cl = "numeric" 94 | switch(cl, 95 | ## selectInput without default value is initially set to NULL 96 | NULL = q, 97 | ## Dates need to be handled separately 98 | Date = format(as.Date(as.numeric(q), "1970-01-01"), "%Y-%m-%d"), 99 | ## default case; should allow to correctly decode TRUE/FALSE 100 | as(q, cl) 101 | ) 102 | } 103 | debugMsg("init", names(queryValues)[i], "->", q) 104 | session$sendInputMessage(names(queryValues)[i], list(value=q)) 105 | } 106 | } 107 | 108 | 109 | .encodeURL = function(session, inputId) { 110 | clientData = isolate(reactiveValuesToList(session$clientData)) 111 | 112 | ## base URL which is not supposed to change 113 | baseURL = paste0(clientData$url_protocol, "//", 114 | clientData$url_hostname, 115 | ## add port number if present 116 | if( (port=clientData$url_port)!="" ) paste0(":", port), 117 | clientData$url_pathname) 118 | 119 | queryString = reactive({ 120 | ## all.names = FALSE excludes objects with a leading dot, in particular the 121 | ## ".url" field to avoid self-dependency 122 | inputValues = reactiveValuesToList(session$input, all.names=FALSE) 123 | 124 | ## quit if there is there are no inputs to encode 125 | if (length(inputValues)==0) return() 126 | 127 | ## remove actionButtons 128 | isActionButton = unlist(lapply(inputValues, inherits, "shinyActionButtonValue"), use.names=FALSE) 129 | inputValues = inputValues[!isActionButton] 130 | 131 | ## remove ggvis specific inputs 132 | idx = grep("_mouse_(over|out)$", names(inputValues)) 133 | if ( length(idx) > 0 ) inputValues = inputValues[-idx] 134 | 135 | inputValues = mapply(function(name, value) { 136 | ## this is important to be able to have all checkboxes unchecked 137 | if (is.null(value)) 138 | "" 139 | else { 140 | if (length(value) == 1L) { 141 | ## encode TRUE/FALSE as T/F 142 | if (is.logical(value)) { 143 | if (isTRUE(value)) "T" else "F" 144 | } 145 | else value 146 | } 147 | else { 148 | cl = class(value) 149 | ## expand checkbox group and multiple select vectors 150 | if (cl=="character") { 151 | setNames(as.list(value), sprintf("%s[%s]", name, seq_along(value))) 152 | } 153 | ## encode range vectors as comma separated string 154 | else { 155 | if (cl=="Date") value = as.integer(value) 156 | paste(value, collapse=",") 157 | } 158 | } 159 | } 160 | }, names(inputValues), inputValues, SIMPLIFY=FALSE) 161 | 162 | ## remove names of sublists before flattening 163 | names(inputValues)[sapply(inputValues, is.list)] = "" 164 | inputValues = unlist(inputValues) 165 | 166 | URLencode(paste(names(inputValues), inputValues, sep = "=", collapse = "&")) 167 | }) 168 | 169 | observe({ 170 | debugMsg(".updateURL") 171 | updateTextInput(session, inputId, value = url()) 172 | updateTextInput(session, ".shinyURL.queryString", value = queryString()) 173 | }, priority = -999) 174 | 175 | url = reactive({ 176 | paste(c(baseURL, queryString()), collapse = "?") 177 | }) 178 | 179 | url 180 | } 181 | 182 | 183 | .queryTinyURL = function(session) { 184 | input = session$input 185 | .busyMsg = "Please wait..." 186 | 187 | ## construct a query string from the current URL 188 | tinyURLquery = eventReactive(input$.getTinyURL, { 189 | sprintf("http://tinyurl.com/api-create.php?url=%s", input[[inputId]]) 190 | }) 191 | 192 | ## set busy message 193 | observeEvent(tinyURLquery(), { 194 | updateTextInput(session, inputId, value=.busyMsg) 195 | 196 | ## resume the observer only after .busyMsg is set 197 | session$onFlushed(function() { 198 | runTinyURLquery$resume() 199 | }) 200 | }) 201 | 202 | ## query TinyURL 203 | runTinyURLquery = observe({ 204 | tinyurl = tryCatch(getURL(tinyURLquery()), error = function(e) "Error fetching tinyURL!") 205 | updateTextInput(session, inputId, value=tinyurl) 206 | runTinyURLquery$suspend() 207 | }, suspended=TRUE) 208 | 209 | invisible() 210 | } 211 | -------------------------------------------------------------------------------- /R/shinyURL.R: -------------------------------------------------------------------------------- 1 | #' Save and restore the view state of a Shiny app 2 | #' 3 | #' Encode the state of Shiny app's widgets into an URL query string, and use 4 | #' parameters from the URL query string to initialize the app. 5 | #' 6 | #' @section Quick setup: To start using shinyURL in your Shiny app, follow these 7 | #' three steps: \enumerate{ \item Load the package in both 'server.R' an 8 | #' ui.R': \code{library("shinyURL")} \item Add a call to \code{ 9 | #' shinyURL.server()} inside the server function \item Add the 10 | #' \code{shinyURL.ui()} widget to the user interface} 11 | #' @author Andrzej Oleś 12 | #' @examples 13 | #' if (interactive()) { 14 | #' library("shiny") 15 | #' 16 | #' ## A Simple Shiny App 17 | #' 18 | #' shinyApp( 19 | #' ui = fluidPage( 20 | #' titlePanel("Hello Shiny!"), 21 | #' sidebarLayout( 22 | #' sidebarPanel( 23 | #' sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30), 24 | #' shinyURL.ui() 25 | #' ), 26 | #' mainPanel( 27 | #' plotOutput("plot") 28 | #' ) 29 | #' ) 30 | #' ), 31 | #' server = function(input, output, session) { 32 | #' shinyURL.server(session) 33 | #' output$plot <- renderPlot({ 34 | #' x <- faithful[, 2] 35 | #' bins <- seq(min(x), max(x), length.out = input$bins + 1) 36 | #' hist(x, breaks = bins, col = 'darkgray', border = 'white') 37 | #' }) 38 | #' } 39 | #' ) 40 | #' 41 | #' ## Shiny Widgets Demo 42 | #' shinyAppDir( system.file('examples', 'widgets', package='shinyURL') ) 43 | #' 44 | #' ## Tabsets Demo 45 | #' shinyAppDir( system.file('examples', 'tabsets', package='shinyURL') ) 46 | #' 47 | #' ## Showcase demo available live at https://gallery.shinyapps.io/shinyURL 48 | #' shinyAppDir( system.file('examples', 'showcase', package='shinyURL') ) 49 | #' 50 | #' ## Interactive R Markdown document which uses a QR code to encode the URL 51 | #' if (require("rmarkdown") && require("qrcode")) 52 | #' run( system.file('examples', 'qrcode', 'qrcode.Rmd', package='shinyURL') ) 53 | #' 54 | #' ## Use with dynamic user interface created by renderUI() 55 | #' shinyAppDir( system.file('examples', 'dynamicUI', package='shinyURL') ) 56 | #' } 57 | #' @name shinyURL 58 | #' @importFrom methods as 59 | #' @importFrom shiny isolate observe parseQueryString observeEvent 60 | #' updateTextInput eventReactive reactiveValuesToList invalidateLater 61 | #' getDefaultReactiveDomain addResourcePath reactive 62 | #' @importFrom shiny tagList tags icon includeScript actionButton div 63 | #' validateCssUnit 64 | #' @importFrom stats setNames 65 | #' @importFrom RCurl getURL 66 | #' @importFrom utils flush.console URLencode 67 | NULL 68 | 69 | inputId=".shinyURL" 70 | -------------------------------------------------------------------------------- /R/ui.R: -------------------------------------------------------------------------------- 1 | #' @details The \code{shinyURL.ui} widget consists of a text field containing an 2 | #' URL to the app's current view state. By default it also features the 3 | #' convenience \sQuote{Copy} button for copying the URL to clipboard, and a 4 | #' \sQuote{TinyURL} button for querying the URL shortening web service. The 5 | #' inclusion of these buttons is optional and can be controlled by the 6 | #' \code{copyURL} and \code{tinyURL} arguments, respectively. 7 | #' 8 | #' The \sQuote{Copy} feature uses the ZeroClipboard library, which provides an 9 | #' easy way to copy text to the clipboard using an invisible Adobe Flash movie 10 | #' and JavaScript. shinyURL includes the JavaScript code to your app 11 | #' automatically, but you also need to have the \dQuote{ZeroClipboard.swf} 12 | #' available to the browser. By default shinyURL uses a local copy distributed 13 | #' with the package; you can override this by setting the 14 | #' \code{ZeroClipboard.swf} argument to \code{shinyURL.ui}, for example, use 15 | #' "//cdnjs.cloudflare.com/ajax/libs/zeroclipboard/2.2.0/ZeroClipboard.swf" 16 | #' for a file hosted on jsDelivr CDN. 17 | #' @param display logical, should the shinyURL widget be displayed 18 | #' @param label Label for the URL field 19 | #' @param width The width of the URL text field, e.g. \code{'100\%'}, or 20 | #' \code{'400px'}; see \code{\link[shiny]{validateCssUnit}}. 21 | #' @param copyURL Include a \sQuote{Copy} button for convenient copying to 22 | #' clipboard 23 | #' @param tinyURL Use the TinyURL web service for shortening the URL 24 | #' @param ZeroClipboard.swf URL of the file ZeroClipboard.swf, as 25 | #' passed to the \sQuote{swfPath} parameter of \sQuote{ZeroClipboard.config}; 26 | #' if missing defaults to the local copy distributed with shinyURL 27 | #' @rdname shinyURL 28 | #' @export 29 | shinyURL.ui = function(display = TRUE, label = "Share URL", width = "100%", copyURL = TRUE, tinyURL = TRUE, ZeroClipboard.swf) { 30 | if (missing(ZeroClipboard.swf)) { 31 | addResourcePath("shinyURL", system.file("zeroclipboard", package = "shinyURL")) 32 | ZeroClipboard.swf = "shinyURL/ZeroClipboard.swf" 33 | } 34 | 35 | tagList( 36 | ## hidden input which stores the URL query string 37 | tags$input(type="text", id=".shinyURL.queryString", style="display: none;"), 38 | tags$script( 39 | type="text/javascript", 40 | "$(\"input[id='.shinyURL.queryString']\").on('shiny:updateinput', function(event) { 41 | window.history.replaceState(null, document.title, '?' + event.message.value); 42 | })"), 43 | 44 | ## shinyURL widget 45 | if (isTRUE(display)) { 46 | div( 47 | class = "form-group shiny-input-container", # same as for textInput 48 | style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"), 49 | 50 | ## URL text field 51 | if (!is.null(label) && !is.na(label)) tags$label(label, `for` = inputId), 52 | tags$input(id = inputId, type="text", class="form-control", value="", style="margin-bottom: 5px;", 53 | title = "URL of the current view state of the app"), 54 | 55 | ## Copy button 56 | if ( isTRUE(copyURL) ) 57 | tagList( 58 | tags$button(id=".copyToClipboard", icon("clipboard"), "Copy", title="Copy to clipboard", type="button", class="btn btn-default", "data-clipboard-target"=inputId), 59 | includeScript(system.file("zeroclipboard", "ZeroClipboard.min.js", package="shinyURL"), type="text/javascript"), 60 | tags$script( 61 | type="text/javascript", 62 | sprintf("ZeroClipboard.config( { swfPath: '%s' } );", ZeroClipboard.swf), 63 | "var client = new ZeroClipboard( document.getElementById('.copyToClipboard') );" 64 | ) 65 | ), 66 | 67 | ## TinyURL button 68 | if ( isTRUE(tinyURL) ) 69 | actionButton(".getTinyURL", "TinyURL", icon=icon("compress"), title="Shorten URL") 70 | ) 71 | } 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | debugMsg = function(...) { 2 | if (isTRUE(getOption("shinyURL.debug"))) { 3 | cat(..., "\n") 4 | flush.console() 5 | } 6 | invisible() 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/aoles/shinyURL.svg)](https://travis-ci.org/aoles/shinyURL) 2 | 3 | ## Installation 4 | 5 | # install.packages("devtools") 6 | devtools::install_github("aoles/shinyURL") 7 | 8 | ## Use 9 | 10 | 1. Load the package in both **server.R** and **ui.R**. 11 | 12 | library("shinyURL") 13 | 14 | 2. In **server.R**, add inside the server function a call to 15 | 16 | shinyURL.server() 17 | 18 | where `session` is the argument passed to the server function. 19 | 20 | 3. Add the shinyURL widget to **ui.R**. 21 | 22 | shinyURL.ui() 23 | 24 | ### Restoring of tabset and navbar panels 25 | 26 | To save and restore active tabs provide the `id` argument to the functions `tabsetPanel` or `navbarPage`. 27 | 28 | ### Disable encoding of certain inputs 29 | 30 | You can suppress certain inputs from being encoded in the query URL by using IDs with a leading dot, e.g. `.inputName`. These inputs won't be restored. 31 | 32 | ## Limitations 33 | 34 | ### Long URLs 35 | 36 | The state of a shiny app gets saved by encoding its input values into an URL. To keep the URL compact and to avoid problems caused by the URL length limit (around 2000 characters) there are some points to keep in mind when developing your app. 37 | 38 | 1. Avoid long names of inputs but rather use short IDs. For example, instead of 39 | 40 | selectInput("firstDrug", "First drug", choices = drugs) 41 | 42 | it's better to have 43 | 44 | selectInput("d1", "First drug", choices = drugs) 45 | 46 | 2. Use named lists for the `choices` argument in `radioButtons` and `checkboxGroupInput`. Then only the names are displayed to the user allowing for shorter values of the control. 47 | 48 | These points are especially relevant for apps with lots of controls. 49 | 50 | ### Action buttons 51 | 52 | Unfortunately, operations performed using action buttons cannot be reliably recorded and restored. 53 | 54 | -------------------------------------------------------------------------------- /inst/examples/dynamicUI/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinyURL) 3 | 4 | # Define UI 5 | ui <- shinyUI(fluidPage( 6 | 7 | # Application title 8 | titlePanel("Dynamic User Interface"), 9 | 10 | # Sidebar with select inputs for data set and variables 11 | sidebarLayout( 12 | sidebarPanel( 13 | selectInput("dataset", "Dataset", 14 | choices = list(Choose = "", "rock", "pressure", "cars")), 15 | uiOutput("varxInput"), 16 | uiOutput("varyInput"), 17 | shinyURL.ui() 18 | ), 19 | 20 | # Show a plot of the selected variables 21 | mainPanel( 22 | plotOutput("plot") 23 | ) 24 | ) 25 | )) 26 | 27 | # Define server logic 28 | server <- shinyServer(function(input, output) { 29 | shinyURL.server() 30 | 31 | # Return the requested dataset 32 | dataset <- reactive({ 33 | switch(input$dataset, 34 | "rock" = rock, 35 | "pressure" = pressure, 36 | "cars" = cars) 37 | }) 38 | 39 | # Variables available in the selected dataset 40 | variables = reactive(names(dataset())) 41 | 42 | # Render select input for the X variable 43 | output$varxInput = renderUI({ 44 | req(input$dataset) 45 | selectInput("varX", label = "X variable", choices = variables()) 46 | }) 47 | 48 | # Render select input for the Y variable 49 | output$varyInput = renderUI({ 50 | req(input$varX %in% variables()) 51 | selectInput("varY", label = "Y variable", 52 | choices = variables()[variables() != input$varX]) 53 | }) 54 | 55 | output$plot <- renderPlot({ 56 | req(input$varY %in% variables()) 57 | plot(x = dataset()[,input$varX], y = dataset()[,input$varY], 58 | xlab = input$varX, ylab = input$varY) 59 | }) 60 | }) 61 | 62 | # Run the application 63 | shinyApp(ui = ui, server = server) 64 | -------------------------------------------------------------------------------- /inst/examples/qrcode/qrcode.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Interactive R Markdown document with QR code" 3 | author: "Andrzej Oleś" 4 | date: "February 10, 2016" 5 | output: html_document 6 | runtime: shiny 7 | --- 8 | 9 | 17 | 18 | It is possible to embed Shiny components in an R Markdown document, resulting in interactive R Markdown documents. More information on how to use these documents is available on the [Shiny](http://shiny.rstudio.com/articles/interactive-docs.html) and [R Markdown](http://rmarkdown.rstudio.com/authoring_shiny.html) websites. 19 | 20 | Even though interactive documents don't explicitly specify an UI and a server, enabling shinyURL is still easy: simply call the function `shinyURL.server` anywhere in your document. 21 | 22 | ```{r shinyURL} 23 | library("shinyURL") 24 | url = shinyURL.server() 25 | ``` 26 | 27 | `shinyURL.server` returns the app's URL in form of an reactive expression. In the example above we store it under the name `url`. Rather than embedding the default UI widget provided by shinyURL, we will use this reactive expression to insert the link inline, as demonstrated in [Share this document](#share) Additionally, with the help of the R package [qrcode](https://cran.r-project.org/web/packages/qrcode/index.html) we generate a [QR code](https://en.wikipedia.org/wiki/QR_code) containing the app's URL. 28 | 29 | ## Sample Inputs and Outputs 30 | 31 | ```{r eruptions, echo=FALSE} 32 | inputPanel( 33 | selectInput("n_breaks", label = "Number of bins:", 34 | choices = c(10, 20, 35, 50), selected = 20), 35 | 36 | sliderInput("bw_adjust", label = "Bandwidth adjustment:", 37 | min = 0.2, max = 2, value = 1, step = 0.2) 38 | ) 39 | 40 | renderPlot({ 41 | hist(faithful$eruptions, probability = TRUE, breaks = as.numeric(input$n_breaks), 42 | xlab = "Duration (minutes)", main = "Geyser eruption duration") 43 | 44 | dens <- density(faithful$eruptions, adjust = input$bw_adjust) 45 | lines(dens, col = "blue") 46 | }) 47 | 48 | ``` 49 | 50 | ## Share this document {#share} 51 | 52 | The current view state of this document can be accessed at `r output$link = renderText( url() ); textOutput("link", container = a)`, or using the QR code below. 53 | 54 | ```{r qrcode, echo=FALSE} 55 | library(qrcode) 56 | 57 | qrcode = reactive( t(qrcode_gen(url(), plotQRcode = FALSE, dataOutput = TRUE, softLimitFlag = FALSE)) ) 58 | nc = reactive( ncol(qrcode()) ) 59 | nr = reactive( nrow(qrcode()) ) 60 | scale = 4 61 | 62 | renderPlot({ 63 | opar = par(mar=c(0,0,0,0)) 64 | on.exit( par(opar) ) 65 | image(1L:nc(), 1L:nr(), qrcode(), xlim = 0.5 + c(0, nc()), 66 | ylim = 0.5 + c(nr(), 0), axes = FALSE, xlab = "", ylab = "", 67 | col = c("white", "black"), asp = 1) 68 | }, width = function() scale*nc(), height = function() scale*nr()) 69 | ``` 70 | -------------------------------------------------------------------------------- /inst/examples/showcase/app.R: -------------------------------------------------------------------------------- 1 | library(shinyURL) 2 | library(markdown) 3 | 4 | variables = list(`Eruption duration` = "eruptions", `Waiting time` = "waiting") 5 | 6 | extractLabel = function(x) { 7 | s <- strsplit(x, " ")[[1L]][2L] 8 | paste0(toupper(substring(s, 1L, 1L)), substring(s, 2L)) 9 | } 10 | 11 | shinyApp( 12 | ui = fluidPage(theme = "style.css", 13 | title = "shinyURL demo", 14 | h1("Old Faithful Geyser Data"), 15 | sidebarLayout( 16 | sidebarPanel( 17 | selectInput("var", "Variable:", variables), 18 | shinyURL.ui(), 19 | div(id = "description", includeMarkdown("description.md")) 20 | ), 21 | mainPanel( 22 | tabsetPanel(id = "tab", 23 | tabPanel("Plot", 24 | plotOutput("plot"), 25 | radioButtons(inputId = "bins", 26 | label = "Number of bins:", 27 | choices = c(10, 20, 35, 50), 28 | selected = 20, 29 | inline = TRUE), 30 | checkboxInput(inputId = "density", 31 | label = strong("Show density estimate"), 32 | value = FALSE), 33 | # Display this only if the density is shown 34 | conditionalPanel(condition = "input.density == true", 35 | sliderInput(inputId = "bandwidth", 36 | label = "Bandwidth adjustment:", 37 | min = 0.5, max = 1.5, value = 1, step = 0.1) 38 | ) 39 | ), 40 | tabPanel("Summary", 41 | h3(textOutput("label")), 42 | verbatimTextOutput("summary") 43 | ) 44 | ) 45 | ) 46 | ) 47 | ), 48 | 49 | server = function(input, output) { 50 | shinyURL.server() 51 | 52 | data <- reactive( faithful[, input$var] ) 53 | 54 | variable <- reactive( names(which(variables==input$var)) ) 55 | 56 | output$plot <- renderPlot({ 57 | x <- data() 58 | var <- variable() 59 | hist(x, 60 | breaks = seq(min(x), max(x), length.out = as.integer(input$bins) + 1), 61 | probability = TRUE, 62 | col = 'skyblue', 63 | border = 'white', 64 | main = var, 65 | xlab = paste(extractLabel(var), "[minutes]")) 66 | if (input$density) 67 | lines(density(x, adjust = input$bandwidth), col = "red", lwd = 2) 68 | }) 69 | 70 | output$label <- renderText(variable()) 71 | 72 | output$summary <- renderPrint(summary(data())) 73 | 74 | } 75 | ) 76 | -------------------------------------------------------------------------------- /inst/examples/showcase/description.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | This app demonstrates how to save and restore the view state of a Shiny app with the help of [shinyURL](https://github.com/aoles/shinyURL). 4 | 5 | The 'Share URL' text field above together with the two buttons below it are rendered by shinyURL. Each time you interact with the app by changing the values of the inputs or by switching between the tabs, the URL in the text field gets updated to reflect the current state of the widgets. 6 | 7 | Your turn: try playing with the controls. Once you're ready, use the 'tinyURL' button to shorten the URL, and copy it to the clipboard with the help of the 'Copy' button. Paste the URL into a new browser tab or window to restore your settings. 8 | 9 | You can also use the following link to load a pre-set configuration: http://tinyurl.com/shinyURL. 10 | 11 | For details on enabling shinyURL in your app, see the package [documentation](https://github.com/aoles/shinyURL/blob/master/README.md). 12 | 13 | Created by Andrzej Oleś, 2016 • Code on [GitHub](https://github.com/aoles/shinyURL/blob/master/inst/examples/showcase) 14 | -------------------------------------------------------------------------------- /inst/examples/showcase/www/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #87b13f; 3 | } 4 | 5 | h2 { 6 | color: #f08000; 7 | font-weight: bold; 8 | font-size: 16px; 9 | } 10 | 11 | h3 { 12 | font-weight: bold; 13 | font-size: 14px; 14 | } 15 | -------------------------------------------------------------------------------- /inst/examples/tabsets/server.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinyURL) 3 | 4 | shinyServer(function(input, output, session) { 5 | shinyURL.server(session) 6 | 7 | # Reactive expression to generate the requested distribution. 8 | data <- reactive({ 9 | dist <- switch(input$dist, norm = rnorm, unif = runif, exp = rexp, rnorm) 10 | dist(input$n) 11 | }) 12 | 13 | # Generate a plot of the data. 14 | output$plot <- renderPlot({ 15 | dist <- input$dist 16 | n <- input$n 17 | hist(data(), main = paste('r', dist, '(', n, ')', sep='')) 18 | }) 19 | 20 | # Generate a summary of the data 21 | output$summary <- renderPrint({ 22 | summary(data()) 23 | }) 24 | 25 | # Generate an HTML table view of the data 26 | output$table <- renderTable({ 27 | data.frame(x=data()) 28 | }) 29 | 30 | }) 31 | -------------------------------------------------------------------------------- /inst/examples/tabsets/ui.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinyURL) 3 | 4 | shinyUI(fluidPage( 5 | titlePanel("Tabsets example"), 6 | 7 | sidebarLayout( 8 | sidebarPanel( 9 | radioButtons("dist", "Distribution type:", 10 | c("Normal" = "norm", 11 | "Uniform" = "unif", 12 | "Exponential" = "exp")), 13 | br(), 14 | sliderInput("n", "Number of observations:", value = 500, min = 1, max = 1000), 15 | hr(), 16 | shinyURL.ui() 17 | ), 18 | 19 | mainPanel( 20 | tabsetPanel(type = "tabs", id = "tab", 21 | tabPanel("Plot", plotOutput("plot")), 22 | tabPanel("Summary", verbatimTextOutput("summary")), 23 | tabPanel("Table", tableOutput("table")) 24 | ) 25 | ) 26 | ) 27 | )) 28 | -------------------------------------------------------------------------------- /inst/examples/widgets/server.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinyURL) 3 | 4 | shinyServer(function(input, output, session) { 5 | shinyURL.server(session) 6 | }) 7 | -------------------------------------------------------------------------------- /inst/examples/widgets/ui.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(shinyURL) 3 | 4 | shinyUI(fluidPage( 5 | 6 | h1("Shiny Widgets Gallery", 7 | style = " 8 | color: #fff; 9 | text-align: center; 10 | padding: 20px; 11 | background-color:#428bca; 12 | margin-top:0px; 13 | border-radius: 0px 0px 4px 4px; 14 | "), 15 | 16 | hr(), 17 | 18 | fluidRow( 19 | 20 | column(4, wellPanel( 21 | h3("Single checkbox"), 22 | checkboxInput("checkbox", label = "Choice A", value = TRUE) 23 | )), 24 | 25 | column(4, wellPanel( 26 | checkboxGroupInput("checkGroup", 27 | label = h3("Checkbox group"), 28 | choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3), 29 | selected = c(1,3)) 30 | )), 31 | 32 | column(4, wellPanel( 33 | radioButtons("radio", 34 | label = h3("Radio buttons"), 35 | choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3), 36 | selected = 1) 37 | )) 38 | 39 | ), 40 | 41 | fluidRow( 42 | 43 | column(4, wellPanel( 44 | dateInput("date", label = h3("Date input")) 45 | )), 46 | 47 | column(4, wellPanel( 48 | dateRangeInput("dates", label = h3("Date range")) 49 | )), 50 | 51 | column(4, wellPanel( 52 | textInput("text", label = h3("Text input"), value = "Enter text...") 53 | )) 54 | 55 | ), 56 | 57 | fluidRow( 58 | 59 | column(4, wellPanel( 60 | numericInput("num", label = h3("Numeric input"), value = 1) 61 | )), 62 | 63 | column(4, wellPanel( 64 | sliderInput("sliderA", label = h3("Slider"), min = 0, max = 100, value = 50) 65 | )), 66 | 67 | column(4, wellPanel( 68 | sliderInput("sliderB", label = h3("Slider range"), min = 0, max = 10, value = c(2.5, 7.5), step = 0.1) 69 | )) 70 | 71 | ), 72 | 73 | fluidRow( 74 | 75 | column(4, wellPanel( 76 | selectInput("select", label = h3("Select box"), choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3), selected = 1) 77 | )), 78 | 79 | column(4, wellPanel( 80 | selectInput("multiselect", label = h3("Multiple select"), choices = list("Choice A" = "a", "Choice B" = "b", "Choice C" = "c"), multiple = TRUE) 81 | )), 82 | 83 | column(4, wellPanel( 84 | shinyURL.ui(), 85 | style = "background-color:#428bca; color:#fff;" 86 | )) 87 | 88 | ) 89 | 90 | )) 91 | -------------------------------------------------------------------------------- /inst/zeroclipboard/ZeroClipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ZeroClipboard 3 | * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. 4 | * Copyright (c) 2009-2014 Jon Rohan, James M. Greene 5 | * Licensed MIT 6 | * http://zeroclipboard.org/ 7 | * v2.2.0 8 | */ 9 | !function(a,b){"use strict";var c,d,e,f=a,g=f.document,h=f.navigator,i=f.setTimeout,j=f.clearTimeout,k=f.setInterval,l=f.clearInterval,m=f.getComputedStyle,n=f.encodeURIComponent,o=f.ActiveXObject,p=f.Error,q=f.Number.parseInt||f.parseInt,r=f.Number.parseFloat||f.parseFloat,s=f.Number.isNaN||f.isNaN,t=f.Date.now,u=f.Object.keys,v=f.Object.defineProperty,w=f.Object.prototype.hasOwnProperty,x=f.Array.prototype.slice,y=function(){var a=function(a){return a};if("function"==typeof f.wrap&&"function"==typeof f.unwrap)try{var b=g.createElement("div"),c=f.unwrap(b);1===b.nodeType&&c&&1===c.nodeType&&(a=f.unwrap)}catch(d){}return a}(),z=function(a){return x.call(a,0)},A=function(){var a,c,d,e,f,g,h=z(arguments),i=h[0]||{};for(a=1,c=h.length;c>a;a++)if(null!=(d=h[a]))for(e in d)w.call(d,e)&&(f=i[e],g=d[e],i!==g&&g!==b&&(i[e]=g));return i},B=function(a){var b,c,d,e;if("object"!=typeof a||null==a||"number"==typeof a.nodeType)b=a;else if("number"==typeof a.length)for(b=[],c=0,d=a.length;d>c;c++)w.call(a,c)&&(b[c]=B(a[c]));else{b={};for(e in a)w.call(a,e)&&(b[e]=B(a[e]))}return b},C=function(a,b){for(var c={},d=0,e=b.length;e>d;d++)b[d]in a&&(c[b[d]]=a[b[d]]);return c},D=function(a,b){var c={};for(var d in a)-1===b.indexOf(d)&&(c[d]=a[d]);return c},E=function(a){if(a)for(var b in a)w.call(a,b)&&delete a[b];return a},F=function(a,b){if(a&&1===a.nodeType&&a.ownerDocument&&b&&(1===b.nodeType&&b.ownerDocument&&b.ownerDocument===a.ownerDocument||9===b.nodeType&&!b.ownerDocument&&b===a.ownerDocument))do{if(a===b)return!0;a=a.parentNode}while(a);return!1},G=function(a){var b;return"string"==typeof a&&a&&(b=a.split("#")[0].split("?")[0],b=a.slice(0,a.lastIndexOf("/")+1)),b},H=function(a){var b,c;return"string"==typeof a&&a&&(c=a.match(/^(?:|[^:@]*@|.+\)@(?=http[s]?|file)|.+?\s+(?: at |@)(?:[^:\(]+ )*[\(]?)((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]?b=c[1]:(c=a.match(/\)@((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]&&(b=c[1]))),b},I=function(){var a,b;try{throw new p}catch(c){b=c}return b&&(a=b.sourceURL||b.fileName||H(b.stack)),a},J=function(){var a,c,d;if(g.currentScript&&(a=g.currentScript.src))return a;if(c=g.getElementsByTagName("script"),1===c.length)return c[0].src||b;if("readyState"in c[0])for(d=c.length;d--;)if("interactive"===c[d].readyState&&(a=c[d].src))return a;return"loading"===g.readyState&&(a=c[c.length-1].src)?a:(a=I())?a:b},K=function(){var a,c,d,e=g.getElementsByTagName("script");for(a=e.length;a--;){if(!(d=e[a].src)){c=null;break}if(d=G(d),null==c)c=d;else if(c!==d){c=null;break}}return c||b},L=function(){var a=G(J())||K()||"";return a+"ZeroClipboard.swf"},M=function(){return null==a.opener&&(!!a.top&&a!=a.top||!!a.parent&&a!=a.parent)}(),N={bridge:null,version:"0.0.0",pluginType:"unknown",disabled:null,outdated:null,sandboxed:null,unavailable:null,degraded:null,deactivated:null,overdue:null,ready:null},O="11.0.0",P={},Q={},R=null,S=0,T=0,U={ready:"Flash communication is established",error:{"flash-disabled":"Flash is disabled or not installed. May also be attempting to run Flash in a sandboxed iframe, which is impossible.","flash-outdated":"Flash is too outdated to support ZeroClipboard","flash-sandboxed":"Attempting to run Flash in a sandboxed iframe, which is impossible","flash-unavailable":"Flash is unable to communicate bidirectionally with JavaScript","flash-degraded":"Flash is unable to preserve data fidelity when communicating with JavaScript","flash-deactivated":"Flash is too outdated for your browser and/or is configured as click-to-activate.\nThis may also mean that the ZeroClipboard SWF object could not be loaded, so please check your `swfPath` configuration and/or network connectivity.\nMay also be attempting to run Flash in a sandboxed iframe, which is impossible.","flash-overdue":"Flash communication was established but NOT within the acceptable time limit","version-mismatch":"ZeroClipboard JS version number does not match ZeroClipboard SWF version number","clipboard-error":"At least one error was thrown while ZeroClipboard was attempting to inject your data into the clipboard","config-mismatch":"ZeroClipboard configuration does not match Flash's reality","swf-not-found":"The ZeroClipboard SWF object could not be loaded, so please check your `swfPath` configuration and/or network connectivity"}},V=["flash-unavailable","flash-degraded","flash-overdue","version-mismatch","config-mismatch","clipboard-error"],W=["flash-disabled","flash-outdated","flash-sandboxed","flash-unavailable","flash-degraded","flash-deactivated","flash-overdue"],X=new RegExp("^flash-("+W.map(function(a){return a.replace(/^flash-/,"")}).join("|")+")$"),Y=new RegExp("^flash-("+W.slice(1).map(function(a){return a.replace(/^flash-/,"")}).join("|")+")$"),Z={swfPath:L(),trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceEnhancedClipboard:!1,flashLoadTimeout:3e4,autoActivate:!0,bubbleEvents:!0,containerId:"global-zeroclipboard-html-bridge",containerClass:"global-zeroclipboard-container",swfObjectId:"global-zeroclipboard-flash-bridge",hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",forceHandCursor:!1,title:null,zIndex:999999999},$=function(a){if("object"==typeof a&&null!==a)for(var b in a)if(w.call(a,b))if(/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(b))Z[b]=a[b];else if(null==N.bridge)if("containerId"===b||"swfObjectId"===b){if(!nb(a[b]))throw new Error("The specified `"+b+"` value is not valid as an HTML4 Element ID");Z[b]=a[b]}else Z[b]=a[b];{if("string"!=typeof a||!a)return B(Z);if(w.call(Z,a))return Z[a]}},_=function(){return Tb(),{browser:C(h,["userAgent","platform","appName"]),flash:D(N,["bridge"]),zeroclipboard:{version:Vb.version,config:Vb.config()}}},ab=function(){return!!(N.disabled||N.outdated||N.sandboxed||N.unavailable||N.degraded||N.deactivated)},bb=function(a,d){var e,f,g,h={};if("string"==typeof a&&a)g=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof d)for(e in a)w.call(a,e)&&"string"==typeof e&&e&&"function"==typeof a[e]&&Vb.on(e,a[e]);if(g&&g.length){for(e=0,f=g.length;f>e;e++)a=g[e].replace(/^on/,""),h[a]=!0,P[a]||(P[a]=[]),P[a].push(d);if(h.ready&&N.ready&&Vb.emit({type:"ready"}),h.error){for(e=0,f=W.length;f>e;e++)if(N[W[e].replace(/^flash-/,"")]===!0){Vb.emit({type:"error",name:W[e]});break}c!==b&&Vb.version!==c&&Vb.emit({type:"error",name:"version-mismatch",jsVersion:Vb.version,swfVersion:c})}}return Vb},cb=function(a,b){var c,d,e,f,g;if(0===arguments.length)f=u(P);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)w.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Vb.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=P[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return Vb},db=function(a){var b;return b="string"==typeof a&&a?B(P[a])||null:B(P)},eb=function(a){var b,c,d;return a=ob(a),a&&!vb(a)?"ready"===a.type&&N.overdue===!0?Vb.emit({type:"error",name:"flash-overdue"}):(b=A({},a),tb.call(this,b),"copy"===a.type&&(d=Db(Q),c=d.data,R=d.formatMap),c):void 0},fb=function(){var a=N.sandboxed;if(Tb(),"boolean"!=typeof N.ready&&(N.ready=!1),N.sandboxed!==a&&N.sandboxed===!0)N.ready=!1,Vb.emit({type:"error",name:"flash-sandboxed"});else if(!Vb.isFlashUnusable()&&null===N.bridge){var b=Z.flashLoadTimeout;"number"==typeof b&&b>=0&&(S=i(function(){"boolean"!=typeof N.deactivated&&(N.deactivated=!0),N.deactivated===!0&&Vb.emit({type:"error",name:"flash-deactivated"})},b)),N.overdue=!1,Bb()}},gb=function(){Vb.clearData(),Vb.blur(),Vb.emit("destroy"),Cb(),Vb.off()},hb=function(a,b){var c;if("object"==typeof a&&a&&"undefined"==typeof b)c=a,Vb.clearData();else{if("string"!=typeof a||!a)return;c={},c[a]=b}for(var d in c)"string"==typeof d&&d&&w.call(c,d)&&"string"==typeof c[d]&&c[d]&&(Q[d]=c[d])},ib=function(a){"undefined"==typeof a?(E(Q),R=null):"string"==typeof a&&w.call(Q,a)&&delete Q[a]},jb=function(a){return"undefined"==typeof a?B(Q):"string"==typeof a&&w.call(Q,a)?Q[a]:void 0},kb=function(a){if(a&&1===a.nodeType){d&&(Lb(d,Z.activeClass),d!==a&&Lb(d,Z.hoverClass)),d=a,Kb(a,Z.hoverClass);var b=a.getAttribute("title")||Z.title;if("string"==typeof b&&b){var c=Ab(N.bridge);c&&c.setAttribute("title",b)}var e=Z.forceHandCursor===!0||"pointer"===Mb(a,"cursor");Rb(e),Qb()}},lb=function(){var a=Ab(N.bridge);a&&(a.removeAttribute("title"),a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px"),d&&(Lb(d,Z.hoverClass),Lb(d,Z.activeClass),d=null)},mb=function(){return d||null},nb=function(a){return"string"==typeof a&&a&&/^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(a)},ob=function(a){var b;if("string"==typeof a&&a?(b=a,a={}):"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(b=a.type),b){b=b.toLowerCase(),!a.target&&(/^(copy|aftercopy|_click)$/.test(b)||"error"===b&&"clipboard-error"===a.name)&&(a.target=e),A(a,{type:b,target:a.target||d||null,relatedTarget:a.relatedTarget||null,currentTarget:N&&N.bridge||null,timeStamp:a.timeStamp||t()||null});var c=U[a.type];return"error"===a.type&&a.name&&c&&(c=c[a.name]),c&&(a.message=c),"ready"===a.type&&A(a,{target:null,version:N.version}),"error"===a.type&&(X.test(a.name)&&A(a,{target:null,minimumVersion:O}),Y.test(a.name)&&A(a,{version:N.version})),"copy"===a.type&&(a.clipboardData={setData:Vb.setData,clearData:Vb.clearData}),"aftercopy"===a.type&&(a=Eb(a,R)),a.target&&!a.relatedTarget&&(a.relatedTarget=pb(a.target)),qb(a)}},pb=function(a){var b=a&&a.getAttribute&&a.getAttribute("data-clipboard-target");return b?g.getElementById(b):null},qb=function(a){if(a&&/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)){var c=a.target,d="_mouseover"===a.type&&a.relatedTarget?a.relatedTarget:b,e="_mouseout"===a.type&&a.relatedTarget?a.relatedTarget:b,h=Nb(c),i=f.screenLeft||f.screenX||0,j=f.screenTop||f.screenY||0,k=g.body.scrollLeft+g.documentElement.scrollLeft,l=g.body.scrollTop+g.documentElement.scrollTop,m=h.left+("number"==typeof a._stageX?a._stageX:0),n=h.top+("number"==typeof a._stageY?a._stageY:0),o=m-k,p=n-l,q=i+o,r=j+p,s="number"==typeof a.movementX?a.movementX:0,t="number"==typeof a.movementY?a.movementY:0;delete a._stageX,delete a._stageY,A(a,{srcElement:c,fromElement:d,toElement:e,screenX:q,screenY:r,pageX:m,pageY:n,clientX:o,clientY:p,x:o,y:p,movementX:s,movementY:t,offsetX:0,offsetY:0,layerX:0,layerY:0})}return a},rb=function(a){var b=a&&"string"==typeof a.type&&a.type||"";return!/^(?:(?:before)?copy|destroy)$/.test(b)},sb=function(a,b,c,d){d?i(function(){a.apply(b,c)},0):a.apply(b,c)},tb=function(a){if("object"==typeof a&&a&&a.type){var b=rb(a),c=P["*"]||[],d=P[a.type]||[],e=c.concat(d);if(e&&e.length){var g,h,i,j,k,l=this;for(g=0,h=e.length;h>g;g++)i=e[g],j=l,"string"==typeof i&&"function"==typeof f[i]&&(i=f[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=A({},a),sb(i,j,[k],b))}return this}},ub=function(a){var b=null;return(M===!1||a&&"error"===a.type&&a.name&&-1!==V.indexOf(a.name))&&(b=!1),b},vb=function(a){var b=a.target||d||null,f="swf"===a._source;switch(delete a._source,a.type){case"error":var g="flash-sandboxed"===a.name||ub(a);"boolean"==typeof g&&(N.sandboxed=g),-1!==W.indexOf(a.name)?A(N,{disabled:"flash-disabled"===a.name,outdated:"flash-outdated"===a.name,unavailable:"flash-unavailable"===a.name,degraded:"flash-degraded"===a.name,deactivated:"flash-deactivated"===a.name,overdue:"flash-overdue"===a.name,ready:!1}):"version-mismatch"===a.name&&(c=a.swfVersion,A(N,{disabled:!1,outdated:!1,unavailable:!1,degraded:!1,deactivated:!1,overdue:!1,ready:!1})),Pb();break;case"ready":c=a.swfVersion;var h=N.deactivated===!0;A(N,{disabled:!1,outdated:!1,sandboxed:!1,unavailable:!1,degraded:!1,deactivated:!1,overdue:h,ready:!h}),Pb();break;case"beforecopy":e=b;break;case"copy":var i,j,k=a.relatedTarget;!Q["text/html"]&&!Q["text/plain"]&&k&&(j=k.value||k.outerHTML||k.innerHTML)&&(i=k.value||k.textContent||k.innerText)?(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",i),j!==i&&a.clipboardData.setData("text/html",j)):!Q["text/plain"]&&a.target&&(i=a.target.getAttribute("data-clipboard-text"))&&(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",i));break;case"aftercopy":wb(a),Vb.clearData(),b&&b!==Jb()&&b.focus&&b.focus();break;case"_mouseover":Vb.focus(b),Z.bubbleEvents===!0&&f&&(b&&b!==a.relatedTarget&&!F(a.relatedTarget,b)&&xb(A({},a,{type:"mouseenter",bubbles:!1,cancelable:!1})),xb(A({},a,{type:"mouseover"})));break;case"_mouseout":Vb.blur(),Z.bubbleEvents===!0&&f&&(b&&b!==a.relatedTarget&&!F(a.relatedTarget,b)&&xb(A({},a,{type:"mouseleave",bubbles:!1,cancelable:!1})),xb(A({},a,{type:"mouseout"})));break;case"_mousedown":Kb(b,Z.activeClass),Z.bubbleEvents===!0&&f&&xb(A({},a,{type:a.type.slice(1)}));break;case"_mouseup":Lb(b,Z.activeClass),Z.bubbleEvents===!0&&f&&xb(A({},a,{type:a.type.slice(1)}));break;case"_click":e=null,Z.bubbleEvents===!0&&f&&xb(A({},a,{type:a.type.slice(1)}));break;case"_mousemove":Z.bubbleEvents===!0&&f&&xb(A({},a,{type:a.type.slice(1)}))}return/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)?!0:void 0},wb=function(a){if(a.errors&&a.errors.length>0){var b=B(a);A(b,{type:"error",name:"clipboard-error"}),delete b.success,i(function(){Vb.emit(b)},0)}},xb=function(a){if(a&&"string"==typeof a.type&&a){var b,c=a.target||null,d=c&&c.ownerDocument||g,e={view:d.defaultView||f,canBubble:!0,cancelable:!0,detail:"click"===a.type?1:0,button:"number"==typeof a.which?a.which-1:"number"==typeof a.button?a.button:d.createEvent?0:1},h=A(e,a);c&&d.createEvent&&c.dispatchEvent&&(h=[h.type,h.canBubble,h.cancelable,h.view,h.detail,h.screenX,h.screenY,h.clientX,h.clientY,h.ctrlKey,h.altKey,h.shiftKey,h.metaKey,h.button,h.relatedTarget],b=d.createEvent("MouseEvents"),b.initMouseEvent&&(b.initMouseEvent.apply(b,h),b._source="js",c.dispatchEvent(b)))}},yb=function(){var a=Z.flashLoadTimeout;if("number"==typeof a&&a>=0){var b=Math.min(1e3,a/10),c=Z.swfObjectId+"_fallbackContent";T=k(function(){var a=g.getElementById(c);Ob(a)&&(Pb(),N.deactivated=null,Vb.emit({type:"error",name:"swf-not-found"}))},b)}},zb=function(){var a=g.createElement("div");return a.id=Z.containerId,a.className=Z.containerClass,a.style.position="absolute",a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px",a.style.zIndex=""+Sb(Z.zIndex),a},Ab=function(a){for(var b=a&&a.parentNode;b&&"OBJECT"===b.nodeName&&b.parentNode;)b=b.parentNode;return b||null},Bb=function(){var a,b=N.bridge,c=Ab(b);if(!b){var d=Ib(f.location.host,Z),e="never"===d?"none":"all",h=Gb(A({jsVersion:Vb.version},Z)),i=Z.swfPath+Fb(Z.swfPath,Z);c=zb();var j=g.createElement("div");c.appendChild(j),g.body.appendChild(c);var k=g.createElement("div"),l="activex"===N.pluginType;k.innerHTML='"+(l?'':"")+'
 
',b=k.firstChild,k=null,y(b).ZeroClipboard=Vb,c.replaceChild(b,j),yb()}return b||(b=g[Z.swfObjectId],b&&(a=b.length)&&(b=b[a-1]),!b&&c&&(b=c.firstChild)),N.bridge=b||null,b},Cb=function(){var a=N.bridge;if(a){var d=Ab(a);d&&("activex"===N.pluginType&&"readyState"in a?(a.style.display="none",function e(){if(4===a.readyState){for(var b in a)"function"==typeof a[b]&&(a[b]=null);a.parentNode&&a.parentNode.removeChild(a),d.parentNode&&d.parentNode.removeChild(d)}else i(e,10)}()):(a.parentNode&&a.parentNode.removeChild(a),d.parentNode&&d.parentNode.removeChild(d))),Pb(),N.ready=null,N.bridge=null,N.deactivated=null,c=b}},Db=function(a){var b={},c={};if("object"==typeof a&&a){for(var d in a)if(d&&w.call(a,d)&&"string"==typeof a[d]&&a[d])switch(d.toLowerCase()){case"text/plain":case"text":case"air:text":case"flash:text":b.text=a[d],c.text=d;break;case"text/html":case"html":case"air:html":case"flash:html":b.html=a[d],c.html=d;break;case"application/rtf":case"text/rtf":case"rtf":case"richtext":case"air:rtf":case"flash:rtf":b.rtf=a[d],c.rtf=d}return{data:b,formatMap:c}}},Eb=function(a,b){if("object"!=typeof a||!a||"object"!=typeof b||!b)return a;var c={};for(var d in a)if(w.call(a,d))if("errors"===d){c[d]=a[d]?a[d].slice():[];for(var e=0,f=c[d].length;f>e;e++)c[d][e].format=b[c[d][e].format]}else if("success"!==d&&"data"!==d)c[d]=a[d];else{c[d]={};var g=a[d];for(var h in g)h&&w.call(g,h)&&w.call(b,h)&&(c[d][b[h]]=g[h])}return c},Fb=function(a,b){var c=null==b||b&&b.cacheBust===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+t():""},Gb=function(a){var b,c,d,e,g="",h=[];if(a.trustedDomains&&("string"==typeof a.trustedDomains?e=[a.trustedDomains]:"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(e=a.trustedDomains)),e&&e.length)for(b=0,c=e.length;c>b;b++)if(w.call(e,b)&&e[b]&&"string"==typeof e[b]){if(d=Hb(e[b]),!d)continue;if("*"===d){h.length=0,h.push(d);break}h.push.apply(h,[d,"//"+d,f.location.protocol+"//"+d])}return h.length&&(g+="trustedOrigins="+n(h.join(","))),a.forceEnhancedClipboard===!0&&(g+=(g?"&":"")+"forceEnhancedClipboard=true"),"string"==typeof a.swfObjectId&&a.swfObjectId&&(g+=(g?"&":"")+"swfObjectId="+n(a.swfObjectId)),"string"==typeof a.jsVersion&&a.jsVersion&&(g+=(g?"&":"")+"jsVersion="+n(a.jsVersion)),g},Hb=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},Ib=function(){var a=function(a){var b,c,d,e=[];if("string"==typeof a&&(a=[a]),"object"!=typeof a||!a||"number"!=typeof a.length)return e;for(b=0,c=a.length;c>b;b++)if(w.call(a,b)&&(d=Hb(a[b]))){if("*"===d){e.length=0,e.push("*");break}-1===e.indexOf(d)&&e.push(d)}return e};return function(b,c){var d=Hb(c.swfPath);null===d&&(d=b);var e=a(c.trustedDomains),f=e.length;if(f>0){if(1===f&&"*"===e[0])return"always";if(-1!==e.indexOf(b))return 1===f&&b===d?"sameDomain":"always"}return"never"}}(),Jb=function(){try{return g.activeElement}catch(a){return null}},Kb=function(a,b){var c,d,e,f=[];if("string"==typeof b&&b&&(f=b.split(/\s+/)),a&&1===a.nodeType&&f.length>0)if(a.classList)for(c=0,d=f.length;d>c;c++)a.classList.add(f[c]);else if(a.hasOwnProperty("className")){for(e=" "+a.className+" ",c=0,d=f.length;d>c;c++)-1===e.indexOf(" "+f[c]+" ")&&(e+=f[c]+" ");a.className=e.replace(/^\s+|\s+$/g,"")}return a},Lb=function(a,b){var c,d,e,f=[];if("string"==typeof b&&b&&(f=b.split(/\s+/)),a&&1===a.nodeType&&f.length>0)if(a.classList&&a.classList.length>0)for(c=0,d=f.length;d>c;c++)a.classList.remove(f[c]);else if(a.className){for(e=(" "+a.className+" ").replace(/[\r\n\t]/g," "),c=0,d=f.length;d>c;c++)e=e.replace(" "+f[c]+" "," ");a.className=e.replace(/^\s+|\s+$/g,"")}return a},Mb=function(a,b){var c=m(a,null).getPropertyValue(b);return"cursor"!==b||c&&"auto"!==c||"A"!==a.nodeName?c:"pointer"},Nb=function(a){var b={left:0,top:0,width:0,height:0};if(a.getBoundingClientRect){var c=a.getBoundingClientRect(),d=f.pageXOffset,e=f.pageYOffset,h=g.documentElement.clientLeft||0,i=g.documentElement.clientTop||0,j=0,k=0;if("relative"===Mb(g.body,"position")){var l=g.body.getBoundingClientRect(),m=g.documentElement.getBoundingClientRect();j=l.left-m.left||0,k=l.top-m.top||0}b.left=c.left+d-h-j,b.top=c.top+e-i-k,b.width="width"in c?c.width:c.right-c.left,b.height="height"in c?c.height:c.bottom-c.top}return b},Ob=function(a){if(!a)return!1;var b=m(a,null),c=r(b.height)>0,d=r(b.width)>0,e=r(b.top)>=0,f=r(b.left)>=0,g=c&&d&&e&&f,h=g?null:Nb(a),i="none"!==b.display&&"collapse"!==b.visibility&&(g||!!h&&(c||h.height>0)&&(d||h.width>0)&&(e||h.top>=0)&&(f||h.left>=0));return i},Pb=function(){j(S),S=0,l(T),T=0},Qb=function(){var a;if(d&&(a=Ab(N.bridge))){var b=Nb(d);A(a.style,{width:b.width+"px",height:b.height+"px",top:b.top+"px",left:b.left+"px",zIndex:""+Sb(Z.zIndex)})}},Rb=function(a){N.ready===!0&&(N.bridge&&"function"==typeof N.bridge.setHandCursor?N.bridge.setHandCursor(a):N.ready=!1)},Sb=function(a){if(/^(?:auto|inherit)$/.test(a))return a;var b;return"number"!=typeof a||s(a)?"string"==typeof a&&(b=Sb(q(a,10))):b=a,"number"==typeof b?b:"auto"},Tb=function(b){var c,d,e,f=N.sandboxed,g=null;if(b=b===!0,M===!1)g=!1;else{try{d=a.frameElement||null}catch(h){e={name:h.name,message:h.message}}if(d&&1===d.nodeType&&"IFRAME"===d.nodeName)try{g=d.hasAttribute("sandbox")}catch(h){g=null}else{try{c=document.domain||null}catch(h){c=null}(null===c||e&&"SecurityError"===e.name&&/(^|[\s\(\[@])sandbox(es|ed|ing|[\s\.,!\)\]@]|$)/.test(e.message.toLowerCase()))&&(g=!0)}}return N.sandboxed=g,f===g||b||Ub(o),g},Ub=function(a){function b(a){var b=a.match(/[\d]+/g);return b.length=3,b.join(".")}function c(a){return!!a&&(a=a.toLowerCase())&&(/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(a)||"chrome.plugin"===a.slice(-13))}function d(a){a&&(i=!0,a.version&&(l=b(a.version)),!l&&a.description&&(l=b(a.description)),a.filename&&(k=c(a.filename)))}var e,f,g,i=!1,j=!1,k=!1,l="";if(h.plugins&&h.plugins.length)e=h.plugins["Shockwave Flash"],d(e),h.plugins["Shockwave Flash 2.0"]&&(i=!0,l="2.0.0.11");else if(h.mimeTypes&&h.mimeTypes.length)g=h.mimeTypes["application/x-shockwave-flash"],e=g&&g.enabledPlugin,d(e);else if("undefined"!=typeof a){j=!0;try{f=new a("ShockwaveFlash.ShockwaveFlash.7"),i=!0,l=b(f.GetVariable("$version"))}catch(m){try{f=new a("ShockwaveFlash.ShockwaveFlash.6"),i=!0,l="6.0.21"}catch(n){try{f=new a("ShockwaveFlash.ShockwaveFlash"),i=!0,l=b(f.GetVariable("$version"))}catch(o){j=!1}}}}N.disabled=i!==!0,N.outdated=l&&r(l)e;e++)a=g[e].replace(/^on/,""),h[a]=!0,j[a]||(j[a]=[]),j[a].push(d);if(h.ready&&N.ready&&this.emit({type:"ready",client:this}),h.error){for(e=0,f=W.length;f>e;e++)if(N[W[e].replace(/^flash-/,"")]){this.emit({type:"error",name:W[e],client:this});break}c!==b&&Vb.version!==c&&this.emit({type:"error",name:"version-mismatch",jsVersion:Vb.version,swfVersion:c})}}return this},bc=function(a,b){var c,d,e,f,g,h=Xb[this.id],i=h&&h.handlers;if(!i)return this;if(0===arguments.length)f=u(i);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)w.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=i[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return this},cc=function(a){var b=null,c=Xb[this.id]&&Xb[this.id].handlers;return c&&(b="string"==typeof a&&a?c[a]?c[a].slice(0):[]:B(c)),b},dc=function(a){if(ic.call(this,a)){"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(a=A({},a));var b=A({},ob(a),{client:this});jc.call(this,b)}return this},ec=function(a){if(!Xb[this.id])throw new Error("Attempted to clip element(s) to a destroyed ZeroClipboard client instance");a=kc(a);for(var b=0;b0,e=!a.target||d&&-1!==c.indexOf(a.target),f=a.relatedTarget&&d&&-1!==c.indexOf(a.relatedTarget),g=a.client&&a.client===this;return b&&(e||f||g)?!0:!1},jc=function(a){var b=Xb[this.id];if("object"==typeof a&&a&&a.type&&b){var c=rb(a),d=b&&b.handlers["*"]||[],e=b&&b.handlers[a.type]||[],g=d.concat(e);if(g&&g.length){var h,i,j,k,l,m=this;for(h=0,i=g.length;i>h;h++)j=g[h],k=m,"string"==typeof j&&"function"==typeof f[j]&&(j=f[j]),"object"==typeof j&&j&&"function"==typeof j.handleEvent&&(k=j,j=j.handleEvent),"function"==typeof j&&(l=A({},a),sb(j,k,[l],c))}}},kc=function(a){return"string"==typeof a&&(a=[]),"number"!=typeof a.length?[a]:a},lc=function(a){if(a&&1===a.nodeType){var b=function(a){(a||(a=f.event))&&("js"!==a._source&&(a.stopImmediatePropagation(),a.preventDefault()),delete a._source)},c=function(c){(c||(c=f.event))&&(b(c),Vb.focus(a))};a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",b,!1),a.addEventListener("mouseenter",b,!1),a.addEventListener("mouseleave",b,!1),a.addEventListener("mousemove",b,!1),$b[a.zcClippingId]={mouseover:c,mouseout:b,mouseenter:b,mouseleave:b,mousemove:b}}},mc=function(a){if(a&&1===a.nodeType){var b=$b[a.zcClippingId];if("object"==typeof b&&b){for(var c,d,e=["move","leave","enter","out","over"],f=0,g=e.length;g>f;f++)c="mouse"+e[f],d=b[c],"function"==typeof d&&a.removeEventListener(c,d,!1);delete $b[a.zcClippingId]}}};Vb._createClient=function(){_b.apply(this,z(arguments))},Vb.prototype.on=function(){return ac.apply(this,z(arguments))},Vb.prototype.off=function(){return bc.apply(this,z(arguments))},Vb.prototype.handlers=function(){return cc.apply(this,z(arguments))},Vb.prototype.emit=function(){return dc.apply(this,z(arguments))},Vb.prototype.clip=function(){return ec.apply(this,z(arguments))},Vb.prototype.unclip=function(){return fc.apply(this,z(arguments))},Vb.prototype.elements=function(){return gc.apply(this,z(arguments))},Vb.prototype.destroy=function(){return hc.apply(this,z(arguments))},Vb.prototype.setText=function(a){if(!Xb[this.id])throw new Error("Attempted to set pending clipboard data from a destroyed ZeroClipboard client instance");return Vb.setData("text/plain",a),this},Vb.prototype.setHtml=function(a){if(!Xb[this.id])throw new Error("Attempted to set pending clipboard data from a destroyed ZeroClipboard client instance");return Vb.setData("text/html",a),this},Vb.prototype.setRichText=function(a){if(!Xb[this.id])throw new Error("Attempted to set pending clipboard data from a destroyed ZeroClipboard client instance");return Vb.setData("application/rtf",a),this},Vb.prototype.setData=function(){if(!Xb[this.id])throw new Error("Attempted to set pending clipboard data from a destroyed ZeroClipboard client instance");return Vb.setData.apply(this,z(arguments)),this},Vb.prototype.clearData=function(){if(!Xb[this.id])throw new Error("Attempted to clear pending clipboard data from a destroyed ZeroClipboard client instance");return Vb.clearData.apply(this,z(arguments)),this},Vb.prototype.getData=function(){if(!Xb[this.id])throw new Error("Attempted to get pending clipboard data from a destroyed ZeroClipboard client instance");return Vb.getData.apply(this,z(arguments))},"function"==typeof define&&define.amd?define(function(){return Vb}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports?module.exports=Vb:a.ZeroClipboard=Vb}(function(){return this||window}()); 10 | //# sourceMappingURL=ZeroClipboard.min.map -------------------------------------------------------------------------------- /inst/zeroclipboard/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aoles/shinyURL/ac5b6834e8d28b560b3523829aba190203d44321/inst/zeroclipboard/ZeroClipboard.swf -------------------------------------------------------------------------------- /man/shinyURL-deprecated.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/deprecated.R 3 | \name{shinyURL.Server} 4 | \alias{shinyURL.Server} 5 | \alias{shinyURL.UI} 6 | \title{Deprecated functions in package \sQuote{shinyURL}} 7 | \usage{ 8 | shinyURL.Server(...) 9 | 10 | shinyURL.UI(...) 11 | } 12 | \arguments{ 13 | \item{...}{Arguments passed to the new methods} 14 | } 15 | \description{ 16 | These functions are provided for compatibility with older versions of 17 | \sQuote{shinyURL} only, and will be defunct at the next release. 18 | } 19 | \details{ 20 | The following functions are deprecated and will be made defunct; use 21 | the replacement indicated below. \itemize{ \item{shinyURL.Server: 22 | \code{\link{shinyURL.server}}} \item{shinyURL.UI: 23 | \code{\link{shinyURL.ui}}} } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /man/shinyURL.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/server.R, R/shinyURL.R, R/ui.R 3 | \name{shinyURL.server} 4 | \alias{shinyURL} 5 | \alias{shinyURL.server} 6 | \alias{shinyURL.ui} 7 | \title{Save and restore the view state of a Shiny app} 8 | \usage{ 9 | shinyURL.server(session, options) 10 | 11 | shinyURL.ui(display = TRUE, label = "Share URL", width = "100\%", 12 | copyURL = TRUE, tinyURL = TRUE, ZeroClipboard.swf) 13 | } 14 | \arguments{ 15 | \item{session}{Typically the same as the optional parameter passed into the 16 | Shiny server function as an argument; if missing defaults to 17 | \code{getDefaultReactiveDomain()}} 18 | 19 | \item{options}{Named list of options} 20 | 21 | \item{display}{logical, should the shinyURL widget be displayed} 22 | 23 | \item{label}{Label for the URL field} 24 | 25 | \item{width}{The width of the URL text field, e.g. \code{'100\%'}, or 26 | \code{'400px'}; see \code{\link[shiny]{validateCssUnit}}.} 27 | 28 | \item{copyURL}{Include a \sQuote{Copy} button for convenient copying to 29 | clipboard} 30 | 31 | \item{tinyURL}{Use the TinyURL web service for shortening the URL} 32 | 33 | \item{ZeroClipboard.swf}{URL of the file ZeroClipboard.swf, as 34 | passed to the \sQuote{swfPath} parameter of \sQuote{ZeroClipboard.config}; 35 | if missing defaults to the local copy distributed with shinyURL} 36 | } 37 | \value{ 38 | \code{shinyURL.server} returns a reactive expression evaluating to 39 | the app's URL. 40 | } 41 | \description{ 42 | Encode the state of Shiny app's widgets into an URL query string, and use 43 | parameters from the URL query string to initialize the app. 44 | } 45 | \details{ 46 | The \code{shinyURL.server} method contains server logic for encoding 47 | and restoring the widgets' values. It is called from inside the app's 48 | server script, and can take the \code{session} objects as argument. 49 | 50 | The argument \code{options} can contain a named list of options. These are 51 | set by a call to \code{\link[base]{options}} as \sQuote{shinyURL.name}. See below for a list of available options. 52 | 53 | The \code{shinyURL.ui} widget consists of a text field containing an 54 | URL to the app's current view state. By default it also features the 55 | convenience \sQuote{Copy} button for copying the URL to clipboard, and a 56 | \sQuote{TinyURL} button for querying the URL shortening web service. The 57 | inclusion of these buttons is optional and can be controlled by the 58 | \code{copyURL} and \code{tinyURL} arguments, respectively. 59 | 60 | The \sQuote{Copy} feature uses the ZeroClipboard library, which provides an 61 | easy way to copy text to the clipboard using an invisible Adobe Flash movie 62 | and JavaScript. shinyURL includes the JavaScript code to your app 63 | automatically, but you also need to have the \dQuote{ZeroClipboard.swf} 64 | available to the browser. By default shinyURL uses a local copy distributed 65 | with the package; you can override this by setting the 66 | \code{ZeroClipboard.swf} argument to \code{shinyURL.ui}, for example, use 67 | "//cdnjs.cloudflare.com/ajax/libs/zeroclipboard/2.2.0/ZeroClipboard.swf" 68 | for a file hosted on jsDelivr CDN. 69 | } 70 | \section{ShinyURL options}{ 71 | 72 | \describe{ 73 | \item{\code{debug = TRUE}}{Print debug messages to the console} 74 | } 75 | } 76 | 77 | \section{Quick setup}{ 78 | To start using shinyURL in your Shiny app, follow these 79 | three steps: \enumerate{ \item Load the package in both 'server.R' an 80 | ui.R': \code{library("shinyURL")} \item Add a call to \code{ 81 | shinyURL.server()} inside the server function \item Add the 82 | \code{shinyURL.ui()} widget to the user interface} 83 | } 84 | \examples{ 85 | if (interactive()) { 86 | library("shiny") 87 | 88 | ## A Simple Shiny App 89 | 90 | shinyApp( 91 | ui = fluidPage( 92 | titlePanel("Hello Shiny!"), 93 | sidebarLayout( 94 | sidebarPanel( 95 | sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 30), 96 | shinyURL.ui() 97 | ), 98 | mainPanel( 99 | plotOutput("plot") 100 | ) 101 | ) 102 | ), 103 | server = function(input, output, session) { 104 | shinyURL.server(session) 105 | output$plot <- renderPlot({ 106 | x <- faithful[, 2] 107 | bins <- seq(min(x), max(x), length.out = input$bins + 1) 108 | hist(x, breaks = bins, col = 'darkgray', border = 'white') 109 | }) 110 | } 111 | ) 112 | 113 | ## Shiny Widgets Demo 114 | shinyAppDir( system.file('examples', 'widgets', package='shinyURL') ) 115 | 116 | ## Tabsets Demo 117 | shinyAppDir( system.file('examples', 'tabsets', package='shinyURL') ) 118 | 119 | ## Showcase demo available live at https://gallery.shinyapps.io/shinyURL 120 | shinyAppDir( system.file('examples', 'showcase', package='shinyURL') ) 121 | 122 | ## Interactive R Markdown document which uses a QR code to encode the URL 123 | if (require("rmarkdown") && require("qrcode")) 124 | run( system.file('examples', 'qrcode', 'qrcode.Rmd', package='shinyURL') ) 125 | 126 | ## Use with dynamic user interface created by renderUI() 127 | shinyAppDir( system.file('examples', 'dynamicUI', package='shinyURL') ) 128 | } 129 | } 130 | \author{ 131 | Andrzej Oleś 132 | } 133 | 134 | --------------------------------------------------------------------------------