emilyriederer / website

Blog / website repo
https://emilyriederer.com
3 stars 1 forks source link

Best practice for modules to check for controls posted by other modules #43

Closed dwadelson closed 2 years ago

dwadelson commented 2 years ago

hi emily, i've been modularizing some shiny code following some of the ideas i first learned from your excellent post. i had a question i thought you might be able to address.

i would like some of my server modules to be able to respond to certain controls if they are present on the dashboard, but have some questions about how best to do this.

in my server modules, i make use of the inputs from the main panel as follows:

dist_plot_server <- function(id,df,info,mainpanelinput) {
  moduleServer(id, function(input, output, session) {
    output$dist_plot <- renderGirafe({
      maininput <- reactiveValuesToList(mainpanelinput())

when i go to make use of those inputs, however, i have to check to see whether they exist or not, or the code will fail if they don't exist. i do this now as follows, for example:

      if  ("excludeoutliers" %in% names(maininput) && maininput$excludeoutliers) {
        selecteddata %<>% filter(!(code %in% outliercodes))
      }

(unfortunately, NULL <> false, so the check is necessary.)

in any case, this can become rather awful rather quickly if i am composing apps using various controls which may or may not be included. for example, the app i'm doing this for is one that lets my physiology students examine their own pulse and blood pressure data and data of their classmates that has been collected according to a specified protocol. last semester, i had a student with a cardiovascular condition that caused her values to be far outside the distribution and, given the very small n in these experiments, i wanted a way to allow the students to exclude her data when doing some of the analyses. this semester, i do not want to include that control on the panel, and so found i had to go through each of the server modules i had and add the check on whether the control did or did not exist.

what i'd like to be able to do is to pass in various conditional tests and code that executes to filter the dataset based on those tests as inputs to the server modules, so i only have to write them once, rather than going into each server module and duplicating code like that above to filter the data. i suppose i could simply filter it on input, but i think the issue is more general than that, since different modules may want to do different things based on the conditions.

in any case, i was wondering what you would advise as best practices for handling situations where modules that have been written with the hope of being reusable in composing different displays for different courses may want to display different controls and respond accordingly. any advise much appreciated.

emilyriederer commented 2 years ago

Hi @dwadelson !

If I could, I may try to answer this technical question with more of a design answer. When building modules, I tend to approach things largely like functions. Drawing from the "Unix philosophy", I tend to aim for the "do ~one~ a few things and do them well" approach.

Taking your use case as an example, I think I would be inclined to separate the functionality out so that there is less conditional logic within any one module. For example:

I think this will make the modules more maintainable, readable, and hopefully easier to write!

On the technical side, the "Shiny modules" chapter of Hadley's new Mastering Shiny book gives some good examples of more advanced module parameters.

Hope that helps!

dwadelson commented 2 years ago

it does...i was trying to make too many smart modules i suppose, and instead i should use a kind of dispatch module instead to call less 'smart' modules...tx