rstudio / rmarkdown

Dynamic Documents for R
https://rmarkdown.rstudio.com
GNU General Public License v3.0
2.86k stars 969 forks source link

Unable to select nothing value for select param with multiple and initial value #1674

Open gadenbuie opened 4 years ago

gadenbuie commented 4 years ago

In a parameterized document, when the parameter input type is select and multiple: true is enabled and an initial value is set, it's not possible to select a null (or nothing/empty) option.

Here's a small reprex that creates two options. Launch Knit with parameters and deselect a and b in the first option. When knitting the document, params$mult_with_initial will be c("a", "b") rather than the expected "".

---
title: "Null Selection Issue"
output: html_document
params:
  mult_with_initial:
    label: Choose Multiple or None (starts selected, deselect options)
    value: ["a", "b"]
    input: select
    multiple: true
    choices: !r c("No selection" = "", letters[1:2])
  mult_start_empty:
    label: Choose Multiple or None (starts empty)
    value: ""
    input: select
    multiple: true
    choices: !r c("No selection" = "", letters[1:2])
---

Deselect `a` and `b` in the first option, 
but note that they are added back as the default selection.

```{r}
str(params)

Expected behavior

str(list(mult_with_initial = "", mult_start_empty = ""))

After removing `a` and `b` from the selection...

![image](https://user-images.githubusercontent.com/5420529/67107877-4b79a900-f19b-11e9-8744-201339fcb6b2.png)

...the resulting document shows:

![image](https://user-images.githubusercontent.com/5420529/67107981-7e23a180-f19b-11e9-8002-32c3319dae40.png)

<details><summary>Session Info</summary>

─ Session info ─────────────────────────────────────────────────────────────────────────── setting value
version R version 3.6.1 (2019-07-05) os macOS Mojave 10.14.6
system x86_64, darwin15.6.0
ui RStudio
language (EN)
collate en_US.UTF-8
ctype en_US.UTF-8
tz America/New_York
date 2019-10-18

─ Packages ─────────────────────────────────────────────────────────────────────────────── package version date lib source
assertthat 0.2.1 2019-03-21 [1] CRAN (R 3.6.0)
backports 1.1.5 2019-10-02 [1] CRAN (R 3.6.0)
callr 3.3.2 2019-09-22 [1] CRAN (R 3.6.0)
cli 1.1.0 2019-03-19 [1] CRAN (R 3.6.0)
clisymbols 1.2.0 2017-05-21 [1] CRAN (R 3.6.0)
crayon 1.3.4 2017-09-16 [1] CRAN (R 3.6.0)
desc 1.2.0 2018-05-01 [1] CRAN (R 3.6.0)
devtools
2.1.0 2019-07-06 [1] CRAN (R 3.6.0)
digest 0.6.21 2019-09-20 [1] CRAN (R 3.6.0)
evaluate 0.14 2019-05-28 [1] CRAN (R 3.6.0)
fastmap 1.0.1 2019-10-08 [1] CRAN (R 3.6.0)
fs 1.3.1 2019-05-06 [1] CRAN (R 3.6.0)
glue 1.3.1 2019-03-12 [1] CRAN (R 3.6.0)
grkstyle 0.0.1 2019-08-12 [1] Github (gadenbuie/grkstyle@a141d39) htmltools 0.4.0 2019-10-04 [1] CRAN (R 3.6.0)
httpuv 1.5.2 2019-09-11 [1] CRAN (R 3.6.0)
jsonlite 1.6 2018-12-07 [1] CRAN (R 3.6.0)
knitr 1.25 2019-09-18 [1] CRAN (R 3.6.0)
later 1.0.0 2019-10-04 [1] CRAN (R 3.6.0)
magrittr 1.5 2014-11-22 [1] CRAN (R 3.6.0)
memoise 1.1.0 2017-04-21 [1] CRAN (R 3.6.0)
mime 0.7 2019-06-11 [1] CRAN (R 3.6.0)
packrat 0.5.0-21 2019-09-30 [1] Github (rstudio/packrat@fe551ea)
pkgbuild 1.0.6 2019-10-09 [1] CRAN (R 3.6.0)
pkgload 1.0.2 2018-10-29 [1] CRAN (R 3.6.0)
prettyunits 1.0.2 2015-07-13 [1] CRAN (R 3.6.0)
processx 3.4.1 2019-07-18 [1] CRAN (R 3.6.0)
promises 1.1.0 2019-10-04 [1] CRAN (R 3.6.0)
prompt 1.0.0 2019-08-12 [1] Github (gaborcsardi/prompt@b332c42) ps 1.3.0 2018-12-21 [1] CRAN (R 3.6.0)
R6 2.4.0 2019-02-14 [1] CRAN (R 3.6.0)
Rcpp 1.0.2 2019-07-25 [1] CRAN (R 3.6.0)
remotes 2.1.0 2019-06-24 [1] CRAN (R 3.6.0)
rlang 0.4.0 2019-06-25 [1] CRAN (R 3.6.0)
rmarkdown 1.16 2019-10-01 [1] CRAN (R 3.6.0)
rprojroot 1.3-2 2018-01-03 [1] CRAN (R 3.6.0)
rsconnect 0.8.15 2019-07-22 [1] CRAN (R 3.6.0)
rsthemes 0.0.2 2019-10-14 [1] Github (gadenbuie/rsthemes@eaded46) rstudioapi 0.10.0-9003 2019-10-14 [1] Github (rstudio/rstudioapi@206d524) sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 3.6.0)
shiny
1.4.0 2019-10-10 [1] CRAN (R 3.6.0)
testthat 2.2.1 2019-07-25 [1] CRAN (R 3.6.0)
usethis * 1.5.1 2019-07-04 [1] CRAN (R 3.6.0)
withr 2.1.2 2018-03-15 [1] CRAN (R 3.6.0)
xfun 0.10 2019-10-01 [1] CRAN (R 3.6.0)
xtable 1.8-4 2019-04-21 [1] CRAN (R 3.6.0)
yaml 2.2.0 2018-07-25 [1] CRAN (R 3.6.0)

[1] /Users/4468739/Library/R/3.6/library [2] /Library/Frameworks/R.framework/Versions/3.6/Resources/library


</details>
cderv commented 2 years ago

A bit of context an explanation about this issues.

There is a special handling in the shiny app for params selection https://github.com/rstudio/rmarkdown/blob/0af6b3556adf6e393b2da23c66c695724ea7bd2d/R/params.R#L413-L424 This means that when the input return NULL or same as the default, it will not be in the return value list values

values is what is returned by the app https://github.com/rstudio/rmarkdown/blob/0af6b3556adf6e393b2da23c66c695724ea7bd2d/R/params.R#L434 https://github.com/rstudio/rmarkdown/blob/0af6b3556adf6e393b2da23c66c695724ea7bd2d/R/params.R#L15-L17

and we'll be merged with initial params from yaml https://github.com/rstudio/rmarkdown/blob/0af6b3556adf6e393b2da23c66c695724ea7bd2d/R/params.R#L37

So as input is empty, the mult_with_initial will not be part of value and so when list are merged the default value will be set in params which is what is return in the example above.

I believe this is also like that because selectInput() does not return empty string among the choices "" but returns NULL as nothing is selected.

Small shiny apps

library(shiny)
shinyApp(
  ui = fluidPage(
    selectInput("choice", "Choose Multiple or None (starts selected, deselect options)",
                c(letters[1:2], "No selection" = ""), 
                selected = c("a", "b"), 
                multiple = TRUE 
    ),
    verbatimTextOutput("result")
  ),
  server = function(input, output) {
    output$result <- renderText({
      capture.output(str(input$choice))
    })
  }
)

If I deselect the initial values, I don't get an empty character but I get NULL as a return value from selectInput image

It seems like the expected behavior from the doc (https://shiny.rstudio.com/reference/shiny/1.7.0/selectInput.html#details)

In selectize mode, if the first element in choices has a value of "", its name will be treated as a placeholder prompt. For example: selectInput("letter", "Letter", c("Choose one" = "", LETTERS))

You would have to use selectize = FALSE in this case to make it works. Try with that:

params:
  mult_with_initial:
    label: Choose Multiple or None (starts selected, deselect options)
    value: ["a", "b"]
    input: select
    multiple: true
    choices: !r c("No selection" = "", letters[1:2])
    selectize: false

Selecting no selection will return in the Rmd what you expect. It seems like using selectize expect a value to be selected.

So I don't think there is any issue here with rmarkdown as this behave as it would in shiny.

However, we could consider that in rmarkdown this would be a special case that when NULL is returned from a selectInput control, then we set it to empty string. Is it what you would do in a shiny app ?

What your thoughts based on this little investigation ?

Thanks

gadenbuie commented 2 years ago

Wow I almost recognize this issue! 🤪

I think that the "no selection" intent should be carried through from the parameter selection app. After reading your explanation, I think rather than returning "" for both, it should be NULL, as it would be in a shiny app.

From reading the special handling logic, it seems like there might be scenarios where NULL is a sentinel value indicating that the user hasn't made a selection and so the default should be used. But in the case of the selectizeInput(), I think it would be safe to treat NULL as the returned value and do something similar to this:

default <- list(
  choice = c("a", "b"),
  other = c("d", "e")
)

params <- list()
# User deselected everything from the selectize choices
params["choice"] <- list(NULL)
# Another input still has the "default"
params$other <- NULL

rmarkdown:::merge_lists(default, params)
#> $other
#> [1] "d" "e"
#> 
#> $choice
#> NULL