4

I am in the processing of developing a fairly involved ShinyApp for interacting with data and just wondering what would be a good strategy to factor out code. Ideally having separate scripts for each tab etc and then glue them together in ui.R and server.R. Anyone who has done similar stuff and can share their experience and/or code it would be of great help.

For example how should I be factoring out the code for different tabPanels

shinyUI( navbarPage("Synodos Data Explorer", id="nav",

  tabPanel("Public Data",
           titlePanel("Explore Public Datasets"),
           source("code1.R")
  ),

  tabPanel("Kinome Screens",
           titlePanel("Exploring Kinome Screening Data"),
           source("code2.R")

  )

) #END navbar
) #END ShinyUI
Abhi
  • 6,075
  • 10
  • 41
  • 55

1 Answers1

5

You could use my Ramd package that was designed for this. First devtools::install_github('robertzk/Ramd'). Now you can include arbitrary scripts (in the same "asynchronous modular dependencies" format you see in node.js projects):

ui.r

library(Ramd)
define('public_data', function(public_data) {
  shinyUI( navbarPage("Synodos Data Explorer", id="nav",
    public_data, kinome_screens
  ))
}) 

public_data.r

tabPanel("Public Data",
  titlePanel("Explore Public Datasets"),
  source("code1.R")
)

kinome_screens.r

tabPanel("Kinome Screens",
  titlePanel("Exploring Kinome Screening Data"),
  source("code2.R")
)

Explanation

The define function allows you to include R scripts relative to the directory the currently executing file lives in. For example, if you would like to structure your code into a directory format, you could do:

define('dir/some_file', 'dir2/some_other_file', function(one, two) {
   # You can use one and two, the "return value" (last expression) in the
   # above files
})

Note that proper use of this completely eliminates the use of global variables and will solve both your managing global state and code organizational issues. The main trick behind this is that when you source a file, like this:

# test.r
c(1,2,3) # This will be ignored, since it isn't at the end of the file
list(1, "hello!")

# otherfile.r
x <- source('test.r')$value

Then x is now list(1, "hello!") (in particular, the "return value" of a call to base::source like this will always give the last expression in the file. Everything you want to pass to other files that include this file should be wrapped up at the end, possibly in a list if you want to return multiple things. You can use this to hierarchically nest your complex Shiny project in a sane organizational structure.

Here is a complete working example that illustrates how to structure some code in a directory with Ramd:

### main.r
define('blah/foo', 'blah2/faa', function(one, two) {
  print(one + two)
})

### blah/foo.r
x <- 1
y <- 2
x + y

### blah2/faa.r
z <- 1
w <- 5
w - z

### R console
library(Ramd); source('main.r')
# [1] 7
Robert Krzyzanowski
  • 9,294
  • 28
  • 24