From 5724853cf33b59ee35e8fd03965b275ebe660034 Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Wed, 13 Mar 2024 16:41:14 +0100 Subject: [PATCH 01/12] use restoreInput in update functions --- R/data_extract_filter_module.R | 13 +++++++++---- R/data_extract_single_module.R | 4 +++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index fe2ad7520..32e334279 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -66,6 +66,8 @@ data_extract_filter_srv <- function(id, datasets, filter) { force(filter) logger::log_trace("data_extract_filter_srv initialized with: { filter$dataname } dataset.") + ns <- session$ns + isolate({ # when the filter is initialized with a delayed spec, the choices and selected are NULL # here delayed are resolved and the values are set up @@ -73,13 +75,13 @@ data_extract_filter_srv <- function(id, datasets, filter) { session = session, inputId = "col", choices = filter$vars_choices, - selected = filter$vars_selected + selected = shiny::restoreInput(ns("col"), filter$vars_selected) ) teal.widgets::updateOptionalSelectInput( session = session, inputId = "vals", choices = filter$choices, - selected = filter$selected + selected = shiny::restoreInput(ns("vals"), filter$selected) ) }) @@ -115,14 +117,17 @@ data_extract_filter_srv <- function(id, datasets, filter) { session = session, inputId = "vals", choices = paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous"), - selected = paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous") + selected = shiny::restoreInput( + ns("vals"), + paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous") + ) ) teal.widgets::updateOptionalSelectInput( session = session, inputId = "vals", choices = choices, - selected = selected + selected = shiny::restoreInput(ns("vals"), selected) ) } ) diff --git a/R/data_extract_single_module.R b/R/data_extract_single_module.R index 7250578e3..703b97fdd 100644 --- a/R/data_extract_single_module.R +++ b/R/data_extract_single_module.R @@ -78,6 +78,8 @@ data_extract_single_srv <- function(id, datasets, single_data_extract_spec) { function(input, output, session) { logger::log_trace("data_extract_single_srv initialized with dataset: { single_data_extract_spec$dataname }.") + ns <- session$ns + # ui could be initialized with a delayed select spec so the choices and selected are NULL # here delayed are resolved isolate({ @@ -86,7 +88,7 @@ data_extract_single_srv <- function(id, datasets, single_data_extract_spec) { session = session, inputId = "select", choices = resolved$select$choices, - selected = resolved$select$selected + selected = shiny::restoreInput(ns("select"), resolved$select$selected) ) }) From 727866b6c9d3312bb622a3b0d50bb42f9e915013 Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Tue, 19 Mar 2024 15:05:53 +0100 Subject: [PATCH 02/12] migrate to renderUI in data_extract_filter_module --- R/data_extract_filter_module.R | 112 ++++++++++----------------------- 1 file changed, 34 insertions(+), 78 deletions(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index 32e334279..2a8910781 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -16,28 +16,10 @@ data_extract_filter_ui <- function(filter, id = "filter") { ns <- NS(id) - html_col <- teal.widgets::optionalSelectInput( - inputId = ns("col"), - label = `if`(inherits(filter, "delayed_filter_spec"), NULL, filter$vars_label), - choices = `if`(inherits(filter, "delayed_filter_spec"), NULL, filter$vars_choices), - selected = `if`(inherits(filter, "delayed_filter_spec"), NULL, filter$vars_selected), - multiple = filter$vars_multiple, - fixed = filter$vars_fixed - ) - - html_vals <- teal.widgets::optionalSelectInput( - inputId = ns("vals"), - label = filter$label, - choices = `if`(inherits(filter, "delayed_filter_spec"), NULL, filter$choices), - selected = `if`(inherits(filter, "delayed_filter_spec"), NULL, filter$selected), - multiple = filter$multiple, - fixed = filter$fixed - ) - tags$div( class = "filter_spec", - if (filter$vars_fixed) shinyjs::hidden(html_col) else html_col, - html_vals + uiOutput(ns("html_col_container")), + uiOutput(ns("html_vals_container")) ) } @@ -68,71 +50,45 @@ data_extract_filter_srv <- function(id, datasets, filter) { ns <- session$ns - isolate({ - # when the filter is initialized with a delayed spec, the choices and selected are NULL - # here delayed are resolved and the values are set up - teal.widgets::updateOptionalSelectInput( - session = session, - inputId = "col", + output$html_col_container <- renderUI({ + teal.widgets::optionalSelectInput( + inputId = ns("col"), + label = filter$vars_label, choices = filter$vars_choices, - selected = shiny::restoreInput(ns("col"), filter$vars_selected) - ) - teal.widgets::updateOptionalSelectInput( - session = session, - inputId = "vals", - choices = filter$choices, - selected = shiny::restoreInput(ns("vals"), filter$selected) + selected = filter$vars_selected, + multiple = filter$vars_multiple, + fixed = filter$vars_fixed ) }) - observeEvent( - input$col, - ignoreInit = TRUE, # When observeEvent is initialized input$col is still NULL as it is set few lines above - ignoreNULL = FALSE, # columns could be NULL, then vals should be set to NULL also - handlerExpr = { - if (!rlang::is_empty(input$col)) { - choices <- value_choices( - datasets[[filter$dataname]](), - input$col, - `if`(isTRUE(input$col == attr(filter$choices, "var_choices")), attr(filter$choices, "var_label"), NULL) - ) + output$html_vals_container <- renderUI({ + req(input$col) - selected <- if (!is.null(filter$selected)) { - filter$selected - } else if (filter$multiple) { - choices - } else { - choices[1] - } - } else { - choices <- character(0) - selected <- character(0) - } - dn <- filter$dataname - fc <- paste(input$col, collapse = ", ") - logger::log_trace("data_extract_filter_srv@1 filter dataset: { dn }; filter var: { fc }.") - # In order to force reactivity we run two updates: (i) set up dummy values (ii) set up appropriate values - # It's due to a missing reactivity triggers if new selected value is identical with previously selected one. - teal.widgets::updateOptionalSelectInput( - session = session, - inputId = "vals", - choices = paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous"), - selected = shiny::restoreInput( - ns("vals"), - paste0(input$val, "$_<-_random_text_to_ensure_val_will_be_different_from_previous") - ) - ) + choices <- value_choices( + data = datasets[[filter$dataname]](), + var_choices = input$col, + var_label = `if`(isTRUE(input$col == attr(filter$choices, "var_choices")), attr(filter$choices, "var_label"), NULL) + ) - teal.widgets::updateOptionalSelectInput( - session = session, - inputId = "vals", - choices = choices, - selected = shiny::restoreInput(ns("vals"), selected) - ) + selected <- if (!is.null(filter$selected)) { + filter$selected + } else if (filter$multiple) { + choices + } else { + choices[1L] } - ) - } - ) + + teal.widgets::optionalSelectInput( + inputId = ns("vals"), + label = filter$label, + choices = choices, + selected = selected, + multiple = filter$multiple, + fixed = filter$fixed + ) + }) + + }) } #' Returns the initial values for the `vals` widget of a `filter_spec` object From 55848cb377473cbe38b518dbced0ffcea8ece871 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:08:47 +0000 Subject: [PATCH 03/12] [skip style] [skip vbump] Restyle files --- R/data_extract_filter_module.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index 2a8910781..d0d171134 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -87,8 +87,8 @@ data_extract_filter_srv <- function(id, datasets, filter) { fixed = filter$fixed ) }) - - }) + } + ) } #' Returns the initial values for the `vals` widget of a `filter_spec` object From 741b615f6c4c112c8b825438b6185db6d1f73b3d Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Tue, 19 Mar 2024 15:12:38 +0100 Subject: [PATCH 04/12] linter --- R/data_extract_filter_module.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index d0d171134..3498d8f22 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -67,7 +67,7 @@ data_extract_filter_srv <- function(id, datasets, filter) { choices <- value_choices( data = datasets[[filter$dataname]](), var_choices = input$col, - var_label = `if`(isTRUE(input$col == attr(filter$choices, "var_choices")), attr(filter$choices, "var_label"), NULL) + var_label = if (isTRUE(input$col == attr(filter$choices, "var_choices"))) attr(filter$choices, "var_label") ) selected <- if (!is.null(filter$selected)) { From 578e84713f20f4099ab34f5f5af91b250faa7c50 Mon Sep 17 00:00:00 2001 From: Vedha Viyash <49812166+vedhav@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:39:33 +0530 Subject: [PATCH 05/12] Trigger the reactivity on input$vals when input$col is changed (#208) Part of the PR #207 Triggering the reactivity using `observeEvent` Example to test: ```r pkgload::load_all("../teal.modules.general") pkgload::load_all("../teal.transform") pkgload::load_all("../teal") pkgload::load_all("../teal.widgets") data <- teal_data() data <- within(data, { require(nestcolor) ADSL <- rADSL ADSL$SEX2 <- rADSL$SEX }) datanames(data) <- "ADSL" join_keys(data) <- default_cdisc_join_keys[datanames(data)] app <- init( data = data, modules = modules( tm_g_association( ref = data_extract_spec( dataname = "ADSL", filter = filter_spec(vars = choices_selected(variable_choices("ADSL"), "SEX")), select = select_spec( label = "Select variable:", choices = variable_choices( data[["ADSL"]], c("SEX", "RACE", "COUNTRY", "ARM", "STRATA1", "STRATA2", "ITTFL", "BMRKR2") ), selected = "RACE", fixed = FALSE ) ), vars = data_extract_spec( dataname = "ADSL", select = select_spec( label = "Select variables:", choices = variable_choices( data[["ADSL"]], c("SEX", "RACE", "COUNTRY", "ARM", "STRATA1", "STRATA2", "ITTFL", "BMRKR2") ), selected = "BMRKR2", multiple = TRUE, fixed = FALSE ) ), ggplot2_args = ggplot2_args( labs = list(subtitle = "Plot generated by Association Module") ) ) ) ) shinyApp(app$ui, app$server) ``` --- R/data_extract_filter_module.R | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index 3498d8f22..07e66a744 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -61,9 +61,8 @@ data_extract_filter_srv <- function(id, datasets, filter) { ) }) - output$html_vals_container <- renderUI({ + vals_options <- reactive({ req(input$col) - choices <- value_choices( data = datasets[[filter$dataname]](), var_choices = input$col, @@ -77,16 +76,38 @@ data_extract_filter_srv <- function(id, datasets, filter) { } else { choices[1L] } + selected <- restoreInput(ns("vals"), selected) + list(choices = choices, selected = selected) + }) + output$html_vals_container <- renderUI({ teal.widgets::optionalSelectInput( inputId = ns("vals"), label = filter$label, - choices = choices, - selected = selected, + choices = vals_options()$choices, + selected = vals_options()$selected, multiple = filter$multiple, fixed = filter$fixed ) }) + + # Since we want the input$vals to depend on input$col for downstream calculations, + # we trigger reactivity by reassigning them. Otherwise, when input$col is changed without + # a change in input$val, the downstream computations will not be triggered. + observeEvent(input$col, { + teal.widgets::updateOptionalSelectInput( + session = session, + inputId = "vals", + choices = "", + selected = "" + ) + teal.widgets::updateOptionalSelectInput( + session = session, + inputId = "vals", + choices = vals_options()$choices, + selected = vals_options()$selected + ) + }) } ) } From 7651f4ff06b92f1a3bea9bb1336da0c0bfc3fa52 Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Tue, 19 Mar 2024 19:19:01 +0100 Subject: [PATCH 06/12] change input ids --- R/data_extract_filter_module.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index 07e66a744..ff7c08be0 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -18,8 +18,8 @@ data_extract_filter_ui <- function(filter, id = "filter") { tags$div( class = "filter_spec", - uiOutput(ns("html_col_container")), - uiOutput(ns("html_vals_container")) + uiOutput(ns("col_container")), + uiOutput(ns("vals_container")) ) } @@ -50,7 +50,7 @@ data_extract_filter_srv <- function(id, datasets, filter) { ns <- session$ns - output$html_col_container <- renderUI({ + output$col_container <- renderUI({ teal.widgets::optionalSelectInput( inputId = ns("col"), label = filter$vars_label, @@ -80,7 +80,7 @@ data_extract_filter_srv <- function(id, datasets, filter) { list(choices = choices, selected = selected) }) - output$html_vals_container <- renderUI({ + output$vals_container <- renderUI({ teal.widgets::optionalSelectInput( inputId = ns("vals"), label = filter$label, From 5a0dc7f40f7232851acfab994a5ba167c189d90c Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Tue, 19 Mar 2024 19:26:13 +0100 Subject: [PATCH 07/12] sort of fix unit tests --- tests/testthat/test-data_extract_module.R | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-data_extract_module.R b/tests/testthat/test-data_extract_module.R index 55df2e991..0345b3bbf 100644 --- a/tests/testthat/test-data_extract_module.R +++ b/tests/testthat/test-data_extract_module.R @@ -23,11 +23,18 @@ testthat::test_that("Single filter", { testthat::expect_silent(input <- data_extract_single_ui(id = NULL, data_extract)) testthat::expect_silent(filter <- input$children[[1]]) - testthat::expect_equal(filter$children[[1]]$children[[1]]$attribs, list(class = "shinyjs-hide")) - testthat::expect_equal( - filter$children[[1]]$children[[2]]$children[[4]]$children[[1]]$children[[1]]$children[[2]]$attribs$multiple, - "multiple" + lapply(filter$children[[1]]$children, `[[`, "attribs"), + list( + list( + id = "filter1-col_container", + class = "shiny-html-output" + ), + list( + id = "filter1-vals_container", + class = "shiny-html-output" + ) + ) ) # more tests - check levels of filtered variables From 7ac724e06a0f275355d7fb505b26e77ff451a536 Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski <114988527+chlebowa@users.noreply.github.com> Date: Thu, 21 Mar 2024 08:41:41 +0100 Subject: [PATCH 08/12] isolate vals selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Kałędkowski <6959016+gogonzo@users.noreply.github.com> Signed-off-by: Aleksander Chlebowski <114988527+chlebowa@users.noreply.github.com> --- R/data_extract_filter_module.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index ff7c08be0..c90866a0e 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -85,7 +85,7 @@ data_extract_filter_srv <- function(id, datasets, filter) { inputId = ns("vals"), label = filter$label, choices = vals_options()$choices, - selected = vals_options()$selected, + selected = isolate(vals_options()$selected), multiple = filter$multiple, fixed = filter$fixed ) From dc3d1a9d7188284f9870c368f8ca7e0e57e4a556 Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Thu, 21 Mar 2024 08:47:11 +0100 Subject: [PATCH 09/12] remove shiny prefixes --- R/check_selector.R | 2 +- R/data_extract_module.R | 4 ++-- R/data_extract_single_module.R | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/check_selector.R b/R/check_selector.R index 122f5f4e8..406d7c9dd 100644 --- a/R/check_selector.R +++ b/R/check_selector.R @@ -111,7 +111,7 @@ check_selector <- function(selector) { check_selector_reshape(selector$reshape) check_selector_internal_id(selector$internal_id) }, - error = function(e) shiny::validate(e$message) + error = function(e) validate(e$message) ) invisible(selector) } diff --git a/R/data_extract_module.R b/R/data_extract_module.R index f77e44438..45ddb0ba1 100644 --- a/R/data_extract_module.R +++ b/R/data_extract_module.R @@ -210,7 +210,7 @@ data_extract_ui <- function(id, label, data_extract_spec, is_single_dataset = FA #' check_data_extract_spec_react <- function(datasets, data_extract) { if (!all(unlist(lapply(data_extract, `[[`, "dataname")) %in% datasets$datanames())) { - shiny::validate( + validate( "Error in data_extract_spec setup:\ Data extract spec contains datasets that were not handed over to the teal app." ) @@ -254,7 +254,7 @@ check_data_extract_spec_react <- function(datasets, data_extract) { } )) - if (!is.null(column_return)) shiny::validate(unlist(column_return)) + if (!is.null(column_return)) validate(unlist(column_return)) NULL } diff --git a/R/data_extract_single_module.R b/R/data_extract_single_module.R index 703b97fdd..01051742c 100644 --- a/R/data_extract_single_module.R +++ b/R/data_extract_single_module.R @@ -88,7 +88,7 @@ data_extract_single_srv <- function(id, datasets, single_data_extract_spec) { session = session, inputId = "select", choices = resolved$select$choices, - selected = shiny::restoreInput(ns("select"), resolved$select$selected) + selected = restoreInput(ns("select"), resolved$select$selected) ) }) From bc375f233242c0a3a2e7289ccca364b2edb9cd37 Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Thu, 21 Mar 2024 15:03:14 +0100 Subject: [PATCH 10/12] rewrite data_extract_single_module to renderUI --- R/data_extract_single_module.R | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/R/data_extract_single_module.R b/R/data_extract_single_module.R index 01051742c..7a4559f73 100644 --- a/R/data_extract_single_module.R +++ b/R/data_extract_single_module.R @@ -34,16 +34,7 @@ data_extract_single_ui <- function(id = NULL, single_data_extract_spec) { ) ## select input - extract_spec_select <- single_data_extract_spec$select - if (!is.null(extract_spec_select$fixed)) { - attr(extract_spec_select$fixed, which = "dataname") <- single_data_extract_spec$dataname - } - - select_display <- if (is.null(extract_spec_select)) { - NULL - } else { - data_extract_select_ui(extract_spec_select, id = ns("select")) - } + select_display <- uiOutput(ns("select_container")) ## reshape input extract_spec_reshape <- single_data_extract_spec$reshape @@ -82,14 +73,21 @@ data_extract_single_srv <- function(id, datasets, single_data_extract_spec) { # ui could be initialized with a delayed select spec so the choices and selected are NULL # here delayed are resolved - isolate({ - resolved <- resolve_delayed(single_data_extract_spec, datasets) - teal.widgets::updateOptionalSelectInput( - session = session, - inputId = "select", - choices = resolved$select$choices, - selected = restoreInput(ns("select"), resolved$select$selected) - ) + resolved <- isolate({ + resolve_delayed(single_data_extract_spec, datasets) + }) + + output$select_container <- renderUI({ + extract_spec_select <- resolved$select + if (!is.null(extract_spec_select$fixed)) { + attr(extract_spec_select$fixed, which = "dataname") <- resolved$dataname + } + + select_display <- if (is.null(extract_spec_select)) { + NULL + } else { + data_extract_select_ui(extract_spec_select, id = ns("select")) + } }) for (idx in seq_along(resolved$filter)) { From 05a12b7a582d437cd679d92a9c94f91ba510246f Mon Sep 17 00:00:00 2001 From: Aleksander Chlebowski Date: Thu, 21 Mar 2024 15:03:28 +0100 Subject: [PATCH 11/12] simplify --- R/data_extract_select_module.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/data_extract_select_module.R b/R/data_extract_select_module.R index 63e8c6969..627dbf96d 100644 --- a/R/data_extract_select_module.R +++ b/R/data_extract_select_module.R @@ -18,8 +18,8 @@ data_extract_select_ui <- function(select, id = "select") { teal.widgets::optionalSelectInput( inputId = id, label = select$label, - choices = `if`(inherits(select, "delayed_select_spec"), NULL, select$choices), - selected = `if`(inherits(select, "delayed_select_spec"), NULL, select$selected), + choices = select$choices, + selected = select$selected, multiple = select$multiple, fixed = select$fixed ) From 6d150fd375e3a4675c0e374767fc4fa9adfb510c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ka=C5=82=C4=99dkowski?= Date: Fri, 22 Mar 2024 15:32:32 +0100 Subject: [PATCH 12/12] fix initialization/restoration of filter vals/vars --- R/data_extract_filter_module.R | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/R/data_extract_filter_module.R b/R/data_extract_filter_module.R index c90866a0e..e1f40437c 100644 --- a/R/data_extract_filter_module.R +++ b/R/data_extract_filter_module.R @@ -51,6 +51,7 @@ data_extract_filter_srv <- function(id, datasets, filter) { ns <- session$ns output$col_container <- renderUI({ + logger::log_trace("data_extract_filter_srv@1 setting up filter col input") teal.widgets::optionalSelectInput( inputId = ns("col"), label = filter$vars_label, @@ -61,6 +62,7 @@ data_extract_filter_srv <- function(id, datasets, filter) { ) }) + is_init <- reactiveVal(TRUE) vals_options <- reactive({ req(input$col) choices <- value_choices( @@ -68,24 +70,24 @@ data_extract_filter_srv <- function(id, datasets, filter) { var_choices = input$col, var_label = if (isTRUE(input$col == attr(filter$choices, "var_choices"))) attr(filter$choices, "var_label") ) - - selected <- if (!is.null(filter$selected)) { - filter$selected + selected <- if (shiny::isolate(is_init())) { + shiny::isolate(is_init(FALSE)) + restoreInput(ns("vals"), filter$selected) } else if (filter$multiple) { choices } else { choices[1L] } - selected <- restoreInput(ns("vals"), selected) list(choices = choices, selected = selected) }) output$vals_container <- renderUI({ + logger::log_trace("data_extract_filter_srv@2 updating filter vals") teal.widgets::optionalSelectInput( inputId = ns("vals"), label = filter$label, choices = vals_options()$choices, - selected = isolate(vals_options()$selected), + selected = vals_options()$selected, multiple = filter$multiple, fixed = filter$fixed )