Intro
This vignette is a complete example and shows how to use
faketables
and all of its components.
In this vignette we will create a shiny app that allows us to modify
data from our dataset and then preview each component of our
faketables
object.
There is a more in-depth example available here.
Setup
Load our library.
library(faketables)
#>
#> Attaching package: 'faketables'
#> The following object is masked from 'package:stats':
#>
#> update
To make sure things don’t get out of hand, we first filter down to
just the first six rows. Then, we want the rownames as a column, and
then for the sake of example, we convert qsec
to a list
column where every row is a vector of length two that is the original
value of qsec
both subtracted by and added by
1
. We then select the columns we wish to display.
Creating a faketable
Understanding input_call()
Because faketables
creates the inputs dynamically, it
can’t work with the output of a function, but rather needs to work with
the function itself. This is called a “bare function”.
input_call()
requires a bare function and a list of
args
to be passed to the bare function.
The bare function usually takes the form of the function call, but
without the parenthesis or any of the arguments such as sum
for the sum()
function. Alternatively, a function
definition can be passed. It is important that this match exactly, or
the updated
table will contain all rows.
The args
should be a list of all desired arguments that
would normally be passed to your function, except the
inputId
. Even if one is supplied, it will be ignored.
These can be stored as a variable or passed directly to the
input
argument of col_def()
.
Creating a col_def
First, we need a col_def
for each column we wish to
display. We will name ours c_def
. Each col_def
must have the name
of the column it is being created for,
an input_call
object, a bare function call to
cast
and ensure that the column has the desired class, a
width
. They can also have an optional
display_name
.
There are some things to note here:
- Our label is
NULL
so that the input isn’t labelled for each row, but it doesn’t have to be that way. -
'rowname'
uses an anonymous function that wraps the input itshinyjs::disabled()
. This is so therowname
column can be rendered as part of the table without users being allowed to modify its value. -
'qsec'
uses an anonymous function that wraps the actual casting function. This is becauseqsec
should be a list column, but we still want to ensure the contents of the list match our desired class.
c_def <- list(
col_def(
name = 'rowname',
input = input_call(
fun = \(inputId, ...) { shinyjs::disabled(shiny::textInput(inputId, ...)) },
args = list(label = NULL, placeholder = 'Vehicle Name')
),
cast = as.character,
width = 3,
display_name = 'Vehicle Name'
),
col_def(
name = 'mpg',
input = input_call(
fun = shiny::numericInput,
args = list(label = NULL)
),
cast = as.numeric,
width = 2,
display_name = 'MPG'
),
col_def(
name = 'cyl',
input = input_call(
fun = shiny::selectInput,
args = list(label = NULL, choices = c(4, 6, 8))
),
cast = as.integer,
width = 2,
display_name = 'CYL'
),
col_def(
name = 'qsec',
input = input_call(
fun = shiny::sliderInput,
args = list(label = NULL, min = 10, max = 25)
),
cast = \(x) purrr::map(x, as.numeric),
width = 3,
display_name = 'QSEC'
)
)
Creating a table_def
A table_def
is, at its core, a collection of
col_def
objects that have been neatly organized into a
tibble
. We will name ours t_def
.
t_def <- table_def(c_def)
The Shiny App
Creating the UI
Our UI will be relatively simple as we’re just trying to demonstrate
our faketable
. First, we use faketablesUI()
to
make sure our faketable
is rendered. Then we create a row
of inputs that match those from our col_def
so that we can
input data and press the 'Add Row'
button to add the row.
At the bottom, we have a radio button that lets us switch between each
of our faketable
object tables to preview.
ui <- shiny::fluidPage(
title = 'mtcars',
shinyjs::useShinyjs(),
faketablesUI(),
shiny::tags$h3('Add Row'),
shiny::fluidRow(
shiny::column(
width = 2, shiny::actionButton('add_row', 'Add Row')
),
shiny::column(
width = 3, shiny::textInput('rowname', 'Vehicle Name')
),
shiny::column(
width = 2, shiny::textInput('mpg', 'MPG', value = 50)
),
shiny::column(
width = 2, shiny::selectInput('cyl', 'CYL', c(4, 6, 8))
),
shiny::column(
width = 3, shiny::sliderInput('qsec', 'QSEC', min = 10, max = 25, value = c(10, 25))
)
),
shiny::tags$h3('Preview Table'),
shiny::radioButtons(
'preview',
label = 'Choose Table to Preview',
choices = c('data', 'inserted', 'updated', 'deleted'),
inline = TRUE
),
shiny::tableOutput('preview_table')
)
Creating the Server
The first thing we do is call faketablesServer()
on our
faketable
, f_tab
, and assign it back to
f_tab
. This is no longer a faketables
object,
but a shiny::reactive()
that holds our faketables
object. As such, in order to
access our faketable
, we now have to use
f_tab()
rather than f_tab
. This reactive will
handle all row updating and deleting on its own. In order to support
inserting rows, we need to create an event listener for the
'Add Row'
button and call our faketablesInsert
using our now reactive f_tab
and the new data we want to
insert.
server <- function(input, output, session) {
f_tab <- faketablesServer(faketable = f_tab)
# get the table referenced by the radio buttons
# `object@property_name` is the same as `prop(object, 'property_name')`
preview_table <- shiny::reactive({ S7::prop(f_tab(), input$preview) })
# use an un-exported function to ensure list columns render
output$preview_table <- shiny::renderTable(faketables:::.list_col_to_chr(preview_table()))
shiny::observe({
ins <- tibble::tibble(
'rowname' = input$rowname,
'mpg' = as.numeric(input$mpg),
'cyl' = as.numeric(input$cyl),
'qsec' = list(as.integer(input$qsec))
)
faketablesInsert(reactive_faketable = f_tab, data = ins)
}) |>
shiny::bindEvent(input$add_row)
}
Running the app
shiny::shinyApp(ui, server)