---
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>
