A calculator in a Shiny app
Install the app from GitHub using:
# install.packages("remotes")
remotes::install_github("grddavies/shinycalculator")
Run the app using:
shinycalculator::run_app()
I developed this app as a resource for teaching how to test R code using testthat
and shiny apps using shinytest
.
Have a look here to see how the app is launched in a headless browser session using shinytest (and PhantomJS).
A session is then simulated using the methods associated with the app
object, and the expected behaviour tested using testthat
.
I'd like to point out some things (as well as the tests) which I think are cool about it:
Modular code structure - the calculator is a shiny module itself, which consists of a screen and 20 instances of a calc_button
module, which interact with the screen.
If you want to update how all the buttons work, you only update the two functions in mod_calc_button.R
.
Quasiquotation - to give each button different functionality, one of the arguments passed to mod_calc_button_server()
is a callback function
(kind of - its a call rather than a function). The call is evaluated in the parent scope of the module server every time a button is pressed.
When I first tried to do this, callback would only be evaluated once - it was being consumed by the first button press.
I fixed that with this commit
where callback
is quoted when the module is first called, and then that call can be evaluated without being consumed.
Safe evaluation of user input - rather than reinvent the wheel by constructing my own mathematical expression parser, the calculator uses the R interpreter to evaluate
user input on the server. It would be a pretty major security issue if the user could submit arbitrary instructions to run on our server so
I had a look at how to get around this. I found this rstudio community question
which uses rlang to inspect the user input, step through the calls in the expression, and only evaluate the call if the function belongs to a list of whitelisted functions.
That way the calculator can evaluate (1 + 1) * 10
but not system("sudo rm -rf /")
if the whitelist is set up correctly!
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.