Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add numeric question type #461

Merged
merged 5 commits into from
Jan 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export(question)
export(question_checkbox)
export(question_is_correct)
export(question_is_valid)
export(question_numeric)
export(question_radio)
export(question_text)
export(question_ui_completed)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ learnr (development version)
* An informative error is now thrown when an exercise chunk's body contains nothing, which lets the tutorial author know that something (e.g., empty line(s)) must be present in the chunk body for it to be rendered as an exercise. ([#410](https://github.com/rstudio/learnr/issues/410)) ([#172](https://github.com/rstudio/learnr/issues/172))
* When `exercise.completion=TRUE`, completion is no longer performed inside of quotes. This (intentionally) prevents the student from being able to list files on the R server ([#401](https://github.com/rstudio/learnr/issues/401)).
* Fail gracefully when unable to open an indexedDB store (e.g. in cross-origin iframes in Safari). ([#417](https://github.com/rstudio/learnr/issues/417)).
* When a quiz's question or answer text are not characters, e.g. HTML, `htmltools` tags, numeric, etc., they are now cast to characters for the displayed answer text and the quiz's default loading text ([#450](https://github.com/rstudio/learnr/pull/450)).

learnr 0.10.1
===========
Expand Down
2 changes: 1 addition & 1 deletion R/question_checkbox.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#'
#' @inheritParams question
#' @param ... answers and extra parameters passed onto \code{\link{question}}.
#' @seealso \code{\link{question_radio}}, \code{\link{question_text}}
#' @seealso \code{\link{question_radio}}, \code{\link{question_text}}, \code{\link{question_numeric}}
#' @export
#' @examples
#' question_checkbox(
Expand Down
115 changes: 115 additions & 0 deletions R/question_numeric.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#' Number question
#'
#' Creates a tutorial quiz question requesting a number.
#'
#' @inheritParams question
#' @inheritParams shiny::numericInput
#' @param ... answers and extra parameters passed onto \code{\link{question}}.
#' @seealso \code{\link{question_radio}}, \code{\link{question_checkbox}}, \code{\link{question_text}}
#' @importFrom utils modifyList
#' @export
#' @examples
#' question_numeric(
#' "What is pi rounded to 2 digits?",
#' answer(3, message = "Don't forget to use the digits argument"),
#' answer(3.1, message = "Too few digits"),
#' answer(3.142, message = "Too many digits"),
#' answer(3.14, correct = TRUE),
#' allow_retry = TRUE,
#' min = 3,
#' max = 4,
#' step = 0.01
#' )
question_numeric <- function(
text,
...,
correct = "Correct!",
incorrect = "Incorrect",
try_again = incorrect,
allow_retry = FALSE,
value = NULL,
min = NA,
max = NA,
step = NA,
options = list()
) {
min <- min %||% NA_real_
max <- max %||% NA_real_
step <- step %||% NA_real_

checkmate::assert_numeric(value, len = 1, null.ok = TRUE, any.missing = FALSE)
checkmate::assert_numeric(min, len = 1, null.ok = FALSE)
checkmate::assert_numeric(max, len = 1, null.ok = FALSE)
checkmate::assert_numeric(step, len = 1, null.ok = FALSE, lower = 0, finite = TRUE)

learnr::question(
text = text,
...,
type = "learnr_numeric",
correct = correct,
incorrect = incorrect,
allow_retry = allow_retry,
random_answer_order = FALSE,
options = modifyList(
options,
list(
value = value,
min = min,
max = max,
step = step
)
)
)
}




question_ui_initialize.learnr_numeric <- function(question, value, ...) {
numericInput(
question$ids$answer,
label = question$question,
value = value,
min = question$options$min,
max = question$options$max,
step = question$options$step
)
}


question_is_valid.learnr_numeric <- function(question, value, ...) {
if (is.null(value)) {
return(FALSE)
}
value <- suppressWarnings(as.numeric(value))
!is.na(value)
}


question_is_correct.learnr_numeric <- function(question, value, ...) {
value <- suppressWarnings(as.numeric(value))

if (length(value) == 0 || is.na(value)) {
showNotification("Please enter a number before submitting", type = "error")
req(value)
}

for (ans in question$answers) {
ans_val <- ans$value
if (isTRUE(all.equal(ans_val, value, tolerance = 1e-10))) {
return(mark_as(
ans$correct,
ans$message
))
}
}

if (!is.na(question$options$min) && value < question$options$min) {
return(mark_as(FALSE, paste0("The number is at least ", question$options$min, ".")))
}
if (!is.na(question$options$max) && value > question$options$max) {
return(mark_as(FALSE, paste0("The number is at most ", question$options$max, ".")))
}

mark_as(FALSE, NULL)
}
2 changes: 1 addition & 1 deletion R/question_radio.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#'
#' @inheritParams question
#' @param ... answers and extra parameters passed onto \code{\link{question}}.
#' @seealso \code{\link{question_checkbox}}, \code{\link{question_text}}
#' @seealso \code{\link{question_checkbox}}, \code{\link{question_text}}, \code{\link{question_numeric}}
#' @export
#' @examples
#' question_radio(
Expand Down
10 changes: 7 additions & 3 deletions R/quiz.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
#'
#' Add interactive quiz questions to a tutorial.
#' Each quiz question is executed within a shiny runtime to provide more flexibility in the types of questions offered.
#' There are three default types of quiz questions:
#' There are four default types of quiz questions:
#' \describe{
#' \item{\code{learnr_radio}}{Radio button question. This question type will only allow for a single answer submission by the user. An answer must be marked for the user to submit their answer.}
#' \item{\code{learnr_checkbox}}{Check box question. This question type will allow for one or more answers to be submitted by the user. At least one answer must be marked for the user to submit their answer.}
#' \item{\code{learnr_text}}{Text box question. This question type will allow for free form text to be submitted by the user. At least one non-whitespace character must be added for the user to submit their answer.}
#' \item{\code{learnr_numeric}}{Numeric question. This question type will allow for a number to be submitted by the user. At least one number must be added for the user to submit their answer.}
#' }
#'
#' Note, the print behavior has changed as the runtime is now Shiny based. If \code{question}s and \code{quiz}es are printed in the console, the S3 structure and information will be displayed.
Expand Down Expand Up @@ -103,13 +104,13 @@ quiz <- function(..., caption = "Quiz") {
#' @export
question <- function(text,
...,
type = c("auto", "single", "multiple", "learnr_radio", "learnr_checkbox", "learnr_text"),
type = c("auto", "single", "multiple", "learnr_radio", "learnr_checkbox", "learnr_text", "learnr_numeric"),
correct = "Correct!",
incorrect = "Incorrect",
try_again = incorrect,
message = NULL,
post_message = NULL,
loading = c("**Loading:** ", text, "<br/><br/><br/>"),
loading = c("**Loading:** ", format(text), "<br/><br/><br/>"),
submit_button = "Submit Answer",
try_again_button = "Try Again",
allow_retry = FALSE,
schloerke marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -224,6 +225,9 @@ quiz_text <- function(text) {
return(text)
}
if (!is.null(text)) {
if (!is.character(text)) {
text <- format(text)
}
# convert markdown
md <- markdown::markdownToHTML(
text = text,
Expand Down
45 changes: 43 additions & 2 deletions inst/tutorials/quiz_question/quiz_question.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ quiz(caption = "Quiz 1",

## Basic Question Types

There are three basic types of quiz questions: radio button, checkbox, and text box. Each one allows for one choice, multiple choices, and direct user input respectively.
There are four basic types of quiz questions: radio button, checkbox, text box, and numeric. Each one allows for one choice, multiple choices, and direct user text or numeric input respectively.



Expand Down Expand Up @@ -346,6 +346,47 @@ question_text(
)
```


-------------------

### Numeric

If you'd like users to supply numeric answers, use `question_numeric()`. A minimum of one correct answer is required.

You can provide an initial `value` and a `step` size for the numeric input. Note that the `step` size only affects the numeric input when the up/down arrows are used to increment/decrement the number; users can still manually enter numbers that don't follow the `step` size.

Use `min` and `max` to set the minimum and maximum values. If a user submits a value outside of the min/max bounds, the feedback message will reveal the lower or upper bound.

````markdown
```{r numeric, echo = FALSE}`r ''`
question_numeric(
"What is pi rounded to 2 digits?",
answer(3, message = "Don't forget to use the digits argument"),
answer(3.1, message = "Too few digits"),
answer(3.142, message = "Too many digits"),
answer(3.14, correct = TRUE),
allow_retry = TRUE,
min = 3,
max = 4,
step = 0.01
)
```
````

```{r numeric, echo = FALSE}
question_numeric(
"What is pi rounded to 2 digits?",
answer(3, message = "Don't forget to use the digits argument"),
answer(3.1, message = "Too few digits"),
answer(3.142, message = "Too many digits"),
answer(3.14, correct = TRUE),
allow_retry = TRUE,
min = 3,
max = 4,
step = 0.01
)
```

-------------------

### Extra Arguments
Expand Down Expand Up @@ -424,7 +465,7 @@ question(

## Custom Question Types

`learnr` comes with the three built in question types explained in [Basic Question Types](#section-basic-question-types): `question_radio`, `question_checkbox`, and `question_text`. Similar to `ggplot2` not being able to implement every possible geom, `learnr` cannot implement every possible `question` type.
`learnr` comes with four built in question types explained in [Basic Question Types](#section-basic-question-types): `question_radio`, `question_checkbox`, `question_text`, and `question_numeric`. Similar to `ggplot2` not being able to implement every possible geom, `learnr` cannot implement every possible `question` type.

To address this, `learnr` utilizes five generic [S3 methods](http://adv-r.had.co.nz/S3.html) to define the behavior of a custom tutorial question. Each generic [S3 method](http://adv-r.had.co.nz/S3.html) should correspond to one of `type` values supplied to the question.

Expand Down
2 changes: 1 addition & 1 deletion man/question_checkbox.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions man/question_numeric.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion man/question_radio.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions man/quiz.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading