|
- ---
- output: github_document
- ---
-
- ```{r setup, include=FALSE}
- knitr::opts_chunk$set(eval = FALSE)
-
- github_sha_link <- function(sha) {
- glue::glue("[{sha}](https://github.com/gadenbuie/js4shiny-frappeCharts/commit/{sha})")
- }
-
- get_last_sha <- function() {
- commit <- git2r::commits()[[1]]
- message(commit$summary)
- message(commit$sha)
- clipr::write_clip(glue::glue(
- "`r github_sha_link(\"{commit$sha}\")`"
- ))
- }
- ```
-
-
- # Building a Shiny Input
-
- In this project,
- we're going to create a typing speed app
- using a custom Shiny input.
- The app will give users typing prompts,
- monitor their typing speed,
- and use a Frappe Chart line chart
- to show their speed over time as they type.
-
- ## Setup a folder for our app inside the frappeCharts package
-
- `r github_sha_link("113340074c3af9c2cdf46cd7787829d4ec56bfcf")`
-
- Create the directory `inst/shiny-input-app` and add `app.R` and `typing.js`.
-
- ```{r}
- usethis::use_directory("inst/shiny-input-app")
- file.create("inst/shiny-input-app/app.R")
- file.create("inst/shiny-input-app/typing.js")
- ```
-
- ## Create a basic Shiny app with a typing area
-
- `r github_sha_link("ff3a962f7e6d16a75ebdb620aac0fdfc9949086e")`
-
- We'll start with typical Shiny inputs.
-
- ```r
- library(shiny)
-
- ui <- fluidPage(
- textAreaInput("typing", "Type here..."),
- verbatimTextOutput("debug")
- )
-
- server <- function(input, output, session) {
- output$debug <- renderPrint(input$typing)
- }
-
- shinyApp(ui, server)
- ```
-
- Run this app and type in the box.
-
- ## Create our own typingSpeedInput()
-
- `r github_sha_link("b7108029b58635652ce87f3e1ea9a2c5a6232020")`
-
- Use Shiny's `textAreaInput()` to get the template
- for our own `typingSpeedInput()`
-
- ```{r, eval=TRUE}
- shiny_text_input <- shiny::textAreaInput(
- "INPUT", "LABEL", placeholder = "PLACEHOLDER"
- )
- cat(format(shiny_text_input))
- ```
-
-
- ```{r}
- typingSpeedInput <- function(inputId, label, placeholder = NULL) {
- .label <- label
- htmltools::withTags(
- div(
- class = "form-group typing-speed",
- label(class = "control-label", `for` = inputId, .label),
- textarea(id = inputId, class = "form-control", placeholder = placeholder)
- )
- )
- }
- ```
-
- Two points:
-
- 1. Notice that I used `htmltools::withTags()`,
- which makes it easier to write multiple tags at once.
- But it has the downside of masking the `label` argument of `typingSpeedInput()`.
- Hence, the first line `.label <- label`.
-
- 1. I added `.typing-speed` to our parent container so that we can
- find or style our custom input.
-
- Replace the `textAreaInput()` with our new `typingSpeedInput()` and run the app.
- It works the same!
- Wait, why?
-
- ```{r}
- ui <- fluidPage(
- # textAreaInput("typing", "Type here..."),
- typingSpeedInput("typing", "Type here..."),
- verbatimTextOutput("debug")
- )
- ```
-
- ## Start creating an input binding for `typingSpeedInput()`
-
- Now we can open `typing.js` and create a Shiny input binding.
-
- If you used `js4shiny::snippets_install()`,
- you have a `ShinyInputBinding` snippet that provides a template for you.
- Or you can copy the chunk below.
-
- <details><summary>Shiny Input Binding Template</summary>
-
- ```js
- // Ref: https://shiny.rstudio.com/articles/building-inputs.html
- // Ref: https://github.com/rstudio/shiny/blob/master/srcjs/input_binding.js
-
- const bindingName = new Shiny.InputBinding();
-
- $.extend(bindingName, {
- find: function(scope) {
- // Specify the selector that identifies your input. `scope` is a general
- // parent of your input elements. This function should return the nodes of
- // ALL of the inputs that are inside `scope`. These elements should all
- // have IDs that are used as the inputId on the server side.
- return scope.querySelectorAll("inputBindingSelector");
- },
- getValue: function(el) {
- // For a particular input, this function is given the element containing
- // your input. In this function, find or construct the value that will be
- // returned to Shiny. The ID of `el` is used for the inputId.
-
- // e.g: return el.value
- return 'FIXME';
- },
- setValue: function(el, value) {
- // This method is used for restoring the bookmarked state of your input
- // and allows you to set the input's state without triggering reactivity.
- // Basically, reverses .getValue()
-
- // e.g.; el.value = value
- console.error('bindingName.setValue() is not yet defined');
- },
- receiveMessage: function(el, data) {
- // Given the input's container and data, update the input
- // and its elements to reflect the given data.
- // The messages are sent from R/Shiny via
- // R> session$sendInputMessage(inputId, data)
- console.error('bindingName.receiveMessage() is not yet defined');
-
- // If you want the update to trigger reactivity, trigger a subscribed event
- $(el).trigger("change")
- },
- subscribe: function(el, callback) {
- // Listen to events on your input element. The following block listens to
- // the change event, but you might want to listen to another event.
- // Repeat the block for each event type you want to subscribe to.
-
- $(el).on("change.bindingName", function(e) {
- // Use callback() or callback(true).
- // If using callback(true) the rate policy applies,
- // for example if you need to throttle or debounce
- // the values being sent back to the server.
- callback();
- });
- },
- getRatePolicy: function() {
- return {
- policy: 'debounce', // 'debounce', 'throttle' or 'direct' (default)
- delay: 100 // milliseconds for debounce or throttle
- };
- },
- unsubscribe: function(el) {
- $(el).off(".bindingName");
- }
- });
-
- Shiny.inputBindings.register(bindingName, 'pkgName.bindingName');
- ```
-
- </details>
|