18 Develop custom input widgets
In the previous chapter, we built template dependencies, the page skeleton, as well as containers like cards. Now, it is a great time to integrate new inputs, leveraging all knowledge from Chapter 12.
18.2 Toggle Switch
We implement the toggle switch component. The HTML structure may be inspected here (or in demo/form-elements.html
if you already downloaded the repository locally).
<label class="form-check form-switch">
<input class="form-check-input" type="checkbox" checked>
<span class="form-check-label">Option 1</span>
</label>
Notice that the tabler switch has the checkbox type, which is very similar to the Shiny checkbox (a switch being a checkbox with a different style):
checkboxInput("test", "Test", TRUE)
#> <div class="form-group shiny-input-container">
#> <div class="checkbox">
#> <label>
#> <input id="test" type="checkbox" checked="checked"/>
#> <span>Test</span>
#> </label>
#> </div>
#> </div>
Therefore, we should again be able to build on top of an existing input binding. We create the tabler_switch()
function.
tabler_switch <- function(inputId, label, value = FALSE,
width = NULL) {
# SEE BELOW
}
We start to recover any possible bookmarked value with restoreInput()
:
value <- restoreInput(id = inputId, default = value)
Then, in line with the above HTML structure, we design the input tag. If we want to reuse the shiny::checkboxInput()
binding, we must not forget the type = checkbox
:
.extend(checkboxInputBinding, {
$find: function(scope) {
return $(scope).find('input[type="checkbox"]');
}// other methods
; })
input_tag <- tags$input(
id = inputId,
type = "checkbox",
class = "form-check-input"
)
We conditionally add a checked
attribute depending on the current value
parameter:
if (!is.null(value) && value) {
input_tag <- tagAppendAttributes(
input_tag,
checked = "checked"
)
}
We proceed to the main wrapper creation that has is a label
tag having the form-check form-switch
class as well as an optional width
parameter:
input_wrapper <- tags$label(
class = "form-check form-switch",
style = if (!is.null(width)) {
paste0("width: ", validateCssUnit(width), ";")
}
)
We finally put everything together with tagAppendChildren()
, the whole code being accessible here:
tagAppendChildren(
input_wrapper,
input_tag,
span(class = "form-check-label", label)
)
Besides, we may also create an update_tabler_switch()
function similar to the updateCheckboxInput()
. We leverage OSUICode::dropNulls()
, a function that removes all NULL
elements from a list. If you remember, the session$sendInputMessage
from R is received by the receiveMessage
method on the JavaScript side:
update_tabler_switch <- function (session, inputId,
label = NULL,
value = NULL) {
message <- dropNulls(list(label = label, value = value))
session$sendInputMessage(inputId, message)
}
In the following example, the action button toggles the switch input value when clicked, as shown in Figure 18.2.
### RUN ###
# OSUICode::run_example(
# "tabler/switch",
# package = "OSUICode"
# )
### APP CODE ###
library(shiny)
library(OSUICode)
ui <- tabler_page(
tabler_body(
fluidRow(
tabler_button(
"update",
"Go!",
width = "25%",
class = "mr-2"
),
tabler_switch(
"toggle",
"Switch",
value = TRUE,
width = "25%"
)
)
)
)
server <- function(input, output, session) {
observe(print(input$toggle))
observeEvent(input$update, {
update_tabler_switch(
session,
"toggle",
value = !input$toggle
)
})
}
shinyApp(ui, server)
Et voilà! Two inputs in just a few minutes.
18.4 Exercises
- Have a look at the Tabler documentation about buttons and extend the
tabler_button
function accordingly. - Propose an implementation of the
Advanced selectboxes
shown here and Figure 18.3.
- Cards are a central elements of all templates. So are tabset panels! Try to modify the
tabler_card()
function to create atabler_tab_card()
function, adding tab navigation within the card header.