diff --git a/vignettes/explanation/application-structure.Rmd b/vignettes/explanation/application-structure.Rmd index bbbe91e6..ae6db91a 100644 --- a/vignettes/explanation/application-structure.Rmd +++ b/vignettes/explanation/application-structure.Rmd @@ -8,16 +8,17 @@ vignette: > --- # Philosophy + Shiny comes with a powerful [reactive programming](https://shiny.rstudio.com/articles/reactivity-overview.html) model and a rich set of functions for creating UI widgets or custom [HTML structure](https://shiny.rstudio.com/articles/tag-glossary.html). These features make it possible to quickly build impressive, interactive applications, -but they can also make your code harder to test and reuse. +but they can also make it harder to test and reuse your code. -To address this, we recommend to separate the code which must depend on Shiny +To address this issue, we recommend separating the code that depends on Shiny from the logic which can be expressed without it. -The division makes for more robust and maintainable apps in our experience. +In our experience, this division is crucial for building robust and maintainable applications. To support this separation, Rhino encourages a specific structure for the R sources of your application: @@ -26,19 +27,43 @@ Rhino encourages a specific structure for the R sources of your application: * `view`: Shiny modules and related code. ## Logic -Code which uses reactivity or UI builder functions can be hard to test and reuse. -With proper design it is possible to express most of your application logic -using plain R functions and data structures (like lists, data frames). Use the `logic` directory for code which can be expressed without Shiny. +Every Shiny app may have a different end goal, +but they all generally contain isolatable sections of code +that can expressed as a normal R functions. +This could be data manipulation, generating non-interactive plots and graphs, +or connecting to an external data source, +but outside of definable inputs, it doesn't interact with or rely on Shiny in any way. + +Code that relies upon reactivity or UI builder/markup functions +can be problematic to test and difficult to reuse. +With proper design and understanding of this concept, +it is possible to express most of your application logic +using plain R functions and data structures (like lists, data frames). + ## View -The `view` directory should contain code which describes the user interface of your application. -Use the functions defined in `logic` here and connect them using reactivity. -Structure your application using [Shiny modules](https://shiny.rstudio.com/articles/modules.html). -A typical module can look something like this: -```r +The `view` directory should contain code which describes the user interface of your application +and relies upon the reactive capabilities of Shiny. +Here is where we will use the functions defined in `logic`, +and where the core app functionality will be defined. + +If you are not familiar with [Shiny modules](https://shiny.rstudio.com/articles/modules.html), +please take the time to read up on the concept. +In short, using modules we can isolate paired Shiny UI/Server code, +and we prevent overlap of reactivity +by wrapping all input/output value names with the `ns()` function. +This allows us to "namespace" the running module +and use it multiple times in the same application. +This is a very important concept to shortly summarize, +but if this is new to you just remember that if you want to reference a UI element in the server, +it needs to be namespaced. + +A typical module could be structured like this: + +``` r box::use( shiny[moduleServer, NS, renderText, tagList, textInput, textOutput], ) @@ -63,20 +88,22 @@ server <- function(id) { } ``` - # Minimal `app.R` + A Rhino application comes with a minimal `app.R`: + ```r # Rhino / shinyApp entrypoint. Do not edit. rhino::app() ``` -You should not edit this file and instead write your top-level code in `app/main.R`. -The comment is also important: -thanks to the `shinyApp` string, RStudio recognizes this file as a Shiny application -and shows the "Run" and "Publish" buttons. +It is important that you do not edit this file or use it like a `global.R` file, +and instead write your top-level code in `app/main.R`. +It is also important to note that thanks to the `shinyApp` string in the comment, +RStudio recognizes this file as a Shiny application +and displays the "Run" and "Publish" buttons. -Such a solution gives Rhino full control over app startup. +This approach gives Rhino full control over the startup processes of your application. Steps performed by `rhino::app()` include: 1. Purge box cache, so the app can be reloaded without restarting R session. @@ -85,14 +112,16 @@ Steps performed by `rhino::app()` include: 4. Load the main module / legacy entrypoint. 5. Add head tags (favicon, CSS & JS). -One can wonder if we really need a separate `main.R` file. +It is a fair question to ask if we really need a separate `main.R` file. Couldn't we just define the top-level `ui` and `server` in `app.R` -and pass it to `rhino::app()` as arguments, much like with `shiny::shinyApp()`? +and pass it to `rhino::app()` as arguments as we would with a normal `shiny::shinyApp() call`? -We employ this solution to enforce consistent use of box in the entire application. +The reasoning behind this stucture is to enforce consistent use of the `{box}` modules +throughout the application. A file loaded with `box::use()` can only load other modules/packages with `box::use()`. -On the other hand, Shiny simply sources `app.R`, -so `library()` and `source()` could be used in this single file. +In short, this means that we cannot use the `library()` or `source()` functions in our app. +This is an important distinction from traditional Shiny structure, +where we are simply sourcing `app.R` when the app is loaded. As the entire Rhino application is loaded with `box::use(app/main)`, -all the sources must be properly structured as box modules. +all its sources must be properly structured as box modules.