--- title: "Server functions" linkTitle: "Server functions" type: docs weight: 2 --- {spsComps} has some useful functions for exception catch, expression validation, and more. Even though we say they are Shiny server functions, but in fact most of them can be run without a Shiny server. We have designed the functions to detect whether there is a Shiny server, if not, they will work only in R console as well. ## load package ```{r} library(spsComps) library(magrittr) ``` ## Server components ### `shinyCatch` #### basic The `shinyCatch` function is useful to capture exception. What we mean exception can be `message`, `warning` or `error`. For example ```{r} shinyCatch({ message("This is a message") warning("This is a warning") stop("This is an error") }) ``` You can see all 3 levels are captured inside the [SPS-XX] log on your console. If you run this in your Shiny app, a pop-up message with the corresponding log level message will be displayed in in app, like following: ![shinycatch](../shinycatch.png) So the message on both UI and console is called **dual-end logging** in SPS. #### Shiny off Of course, if you do not want users to see the message, you can hide it by `shiny = FALSE`, but the message will be still logged on R console. Run the following on your own computer and watch the difference. ```{r eval=FALSE} library(shiny) ui <- fluidPage() server <- function(input, output, session) { shinyCatch({ stop("This is an error") }, shiny = FALSE) } shinyApp(ui, server) ``` #### get return `shinyCatch` is able to return you values if your expression has any. Imagine we have a function `addNum` that gives message, warning or error depeend on the input. ```{r} addNum <- function(num){ if (num > 0) {message(num)} else if (num == 0) {warning("Num is 0")} else {stop("less than 0")} return(num + num) } value_a <- shinyCatch({ addNum(1) }) value_a value_b <- shinyCatch({ addNum(0) }) value_b value_c <- shinyCatch({ addNum(-1) }) value_c ``` You can see at `message` and `warning` level, the expected value returned, and at `error` level, the return is `NULL`. So the following code `value_c` still runs and is not blocked by the `error` occurred in `shinyCatch`. #### Blocking level More often, if there is an error, we do want take the log in R console, inform the Shinyapp user and then stop the following code. In this case, we need to specify the `blocking_level`. So, default is `"none"`, do not block and return `NULL` if there is an error, and you can choose `"error"`, `"warning"` or `"message"`. - **error**: block downstream if the first `error` detected in `shinyCatch` - **warning**: block downstream if the first `error` or `warning` detected in `shinyCatch` - **message**: block downstream if the first `error`, `warning` or `message` detected in `shinyCatch` You can see the stringency becomes tighter: message > warning > error Blocking code will generate error, in order to have the Rmd rendered, we wrap the expression in `try` ```{r} try({ shinyCatch({ stop("error level is the most commonly used level") }, blocking_level = "error") print("This will not be evaluated") }) try({ shinyCatch({ message("error level is the most commonly used level") }, blocking_level = "message") print("This will not be evaluated either") }) ``` You can see the following `print` in both cases are not got evaluated. #### Block reactive The most useful case for `shinyCatch` is to use it in the [Shiny reactive](https://shiny.rstudio.com/tutorial/written-tutorial/lesson6/) context. Most errors in shiny::reactive, shiny::observer, shiny::observeEvent, or shiny::renderXXX series function will **crash** the app. With `shinyCatch`, it will not. It "dual-logs" the error and stop downstream code. The following example use `shiny::reactiveConsole()` to mock a Shiny server session ```{r} shiny::reactiveConsole(TRUE) y <- observe({ stop("an error from a function") print("some other process") }) ``` ``` ## Warning: Error in : The value of x is ## 38: stop ## 37: [#2] ## 35: contextFunc ## 34: env$runWith ## 27: ctx$run ## 26: run ## 7: flushCallback ## 6: FUN ## 5: lapply ## 4: ctx$executeFlushCallbacks ## 3: .getReactiveEnvironment()$flush ## 2: flushReact ## 1: ``` It crashes the app. However, if you use `shinyCatch` ```{r} shiny::reactiveConsole(TRUE) y <- observe({ shinyCatch({ stop("an error from a function") }, blocking_level = "error") print("some other process") }) ``` ``` ## [SPS-ERROR] 2021-03-02 22:13:05 an error from a function ``` It only logs the error and prevent the downstream `print` to run. Now try following real Shiny apps and watch the difference: ```{r eval=FALSE} # with shinyCatch library(shiny) server <- function(input, output, session) { observe({ shinyCatch({ stop("an error from a function") }, blocking_level = "error") print("some other process") }) } shinyApp(fluidPage(), server) ``` ```{r eval=FALSE} # without shinyCatch library(shiny) server <- function(input, output, session) { observe({ stop("an error from a function") print("some other process") }) } shinyApp(fluidPage(), server) ``` ### `spsValidate` In data analysis, it is important we do some validations before the downstream process, like make a plot. It is epecially the case in Shiny apps. We cannot predict what the user inputs will be, like what kind of data they will use. Similar to `shinyCatch`, `spsValidate` is able to catch exceptions but more useful to handle validations. In addtion to `shinyCatch` functionalities, it will give users a success message if the expression goes through and return `TRUE` (`shinyCatch` returns the final expression value). ```{r} shiny::reactiveConsole(TRUE) x <- reactiveVal(10) y <- observe({ spsValidate({ # have multiple validations in one expression if (x() == 1) stop("cannot be 1") if (x() == 0) stop("cannot be 0") if (x() < 0) stop("less than 0") }) message("The value of x is ", x()) }) x(0) x(-10) ``` ``` ## The value of x is 10 ## [ ERROR] 2021-03-02 22:36:16 cannot be 0 ## [ ERROR] 2021-03-02 22:36:16 less than 0 ``` Try this real Shiny app: ```{r eval=FALSE} library(shiny) ui <- fluidPage( shiny::sliderInput( "num", "change number", min = -1, max = 2, value = 2, step = 1 ) ) server <- function(input, output, session) { x <- reactive(as.numeric(input$num)) y <- observe({ spsValidate(vd_name = "check numbers", verbose = TRUE, { # have multiple validations in one expression if (x() == 1) stop("cannot be 1") if (x() == 0) stop("cannot be 0") if (x() < 0) stop("less than 0") }) message("The value of x is ", x()) }) } shinyApp(ui, server) ``` You should see the success message like this: ![spsvalidate](../spsvalidate.png) ### `shinyCheckPkg` Sometimes we want the app behave differently if users have certain packages installed. For example, if some packages are installed, we open up additional tabs on UI to allow more features. This can be done with the `shinyCheckPkg` function. This function has to run inside Shiny server, an alternative version to use without Shiny is from the [spsUtil](../../spsutil) package, `spsUtil::checkNameSpace` Use it in Shiny server, specify the packages you want to check by different sources, like CRAN, Bioconductor, or github. ```{r eval=FALSE} shinyCheckPkg(session, cran_pkg = c("pkg1", "pkg2"), bioc_pkg = "bioxxx", github = "user1/pkg1") ``` It will return `TRUE` if all packages are installed, otherwise `FALSE` Now try this real example. We check if the `ggplot99` package is installed, if yes we make a plot. It also combines the `spsValidate` function. You can have a better idea how these functions work. ```{r eval=FALSE} library(shiny) ui <- fluidPage( tags$label('Check if package "pkg1", "pkg2", "bioxxx", github package "user1/pkg1" are installed'), br(), actionButton("check_random_pkg", "check random_pkg"), br(), spsHr(), tags$label('We can combine `spsValidate` to block server code to prevent crash if some packages are not installed.'), br(), tags$label('If "shiny" is installed, make a plot.'), br(), actionButton("check_shiny", "check shiny"), br(), tags$label('If "ggplot99" is installed, make a plot.'), br(), actionButton("check_gg99", "check ggplot99"), br(), plotOutput("plot_pkg") ) server <- function(input, output, session) { observeEvent(input$check_random_pkg, { shinyCheckPkg(session, cran_pkg = c("pkg1", "pkg2"), bioc_pkg = "bioxxx", github = "user1/pkg1") }) observeEvent(input$check_shiny, { spsValidate(verbose = FALSE, { if(!shinyCheckPkg(session, cran_pkg = c("shiny"))) stop("Install packages") }) output$plot_pkg <- renderPlot(plot(1)) }) observeEvent(input$check_gg99, { spsValidate({ if(!shinyCheckPkg(session, cran_pkg = c("ggplot99"))) stop("Install packages") }) output$plot_pkg <- renderPlot(plot(99)) }) } shinyApp(ui, server) ``` You should see something like this if there is any missing package: ![shinycheckpkg](../shinycheckpkg.png) ### In-line operation In-place operations like i += 1, i -= 1 is not support in R. These functions implement these operations in R. This set of functions will apply this kind of operations on [`shiny::reactiveVal`] objects. ```{r} reactiveConsole(TRUE) rv <- reactiveVal(0) incRv(rv) # add 1 rv() incRv(rv) # add 1 rv() incRv(rv, -1) # minus 1 rv() incRv(rv, -1) # minus 1 rv() rv2 <- reactiveVal(1) multRv(rv2) # times 2 rv2() multRv(rv2) # times 2 rv2() diviRv(rv2) # divide 2 rv2() diviRv(rv2) # divide 2 rv2() reactiveConsole(FALSE) ``` If you are looking for inline operations on normal R objects or `shiny::reactiveValues`, check [spsUtil](../../spsutil) ### spsDepend("xx") The function `spsDepend` was required to show add HTML dependencies, so messages can be displayed on UI for server functions like `shinyCatch`. Since spsComps > 0.3, this is no longer required. We rewrote the way to append the dependencies, so it automatically do it in the background. So adding this function on UI becomes optional. There is nothing you need to do if you have the old code. Both ways are working.