4. Case study: create a new field
new-field.Rmd
In this vignette, we show how to create a new field, that is the
slider_field
, similar to the range field with only 1
value.
Introduction
To create a field, we first need to design a
constructor. In our case, we call it
new_slider_field
. Since fields are mirroring shiny inputs,
we can naturally provide the corresponding input parameters that are the
value, minimum, maximum and step. We pass all of these to
blockr::new_field()
and give it a custom class
slider_field
.
new_slider_field <- function(
value = numeric(),
min = numeric(),
max = numeric(),
step = numeric(),
...) {
blockr::new_field(
value = value,
min = min,
max = max,
step = step,
...,
class = "slider_field"
)
}
On the UI side, this is fairly straightforward as all we have to do
is call shiny::sliderInput
with the same parameters given
in the above constructor.
ui_input.slider_field <- function(x, id, name) {
shiny::sliderInput(
blockr::input_ids(x, id),
name,
value = blockr::value(x, "value"),
min = blockr::value(x, "min"),
max = blockr::value(x, "max"),
step = blockr::value(x, "step")
)
}
The next step consists in validating the field to ensure provided
values are correct. For instance, we want to make sure all passed
elements are numbers. This is done within the
validate_field.slider_field
method. We extract all the
field element values with blockr::value(x, "element")
and
use the suitable validate function, that is
validate_number()
. Note that if nothing is provided, the
validation falls back to validate_field.field()
, which does
nothing.
validate_field.slider_field <- function(x) {
val <- value(x)
min <- value(x, "min")
max <- value(x, "max")
step <- value(x, "step")
validate_number(val)
if (length(min)) {
validate_number(min, "min")
}
if (length(max)) {
validate_number(max, "max")
}
if (length(step)) {
validate_number(step, "step")
}
NextMethod()
}
Finally, we want to make sure that we can update the field with the
ui_update.slider_field()
method. This is needed whenever
another field would have to update the slider field, because of a
possible dependency between these 2 fields. As an
example, this is what currently happens in the filter
block where the selected column field updates the value field
(the field type changes depending on the column type).
ui_update.slider_field <- function(x, session, id, name) {
updateSliderInput(
session,
blockr::input_ids(x, id),
blockr::get_field_name(x, name),
blockr::value(x),
blockr::value(x, "min"),
blockr::value(x, "max"),
blockr::value(x, "step")
)
}
Example
We can test our newly created field in a custom block.
library(blockr)
new_slider_field <- function(
value = numeric(),
min = numeric(),
max = numeric(),
step = numeric(),
...) {
blockr::new_field(
value = value,
min = min,
max = max,
step = step,
...,
class = "slider_field"
)
}
ui_input.slider_field <- function(x, id, name) {
shiny::sliderInput(
blockr::input_ids(x, id),
name,
value = blockr::value(x, "value"),
min = blockr::value(x, "min"),
max = blockr::value(x, "max"),
step = blockr::value(x, "step")
)
}
validate_field.slider_field <- function(x) {
val <- value(x)
min <- value(x, "min")
max <- value(x, "max")
step <- value(x, "step")
validate_number(val)
if (length(min)) {
validate_number(min, "min")
}
if (length(max)) {
validate_number(max, "max")
}
if (length(step)) {
validate_number(step, "step")
}
NextMethod()
}
ui_update.slider_field <- function(x, session, id, name) {
updateSliderInput(
session,
blockr::input_ids(x, id),
name,
blockr::value(x),
blockr::value(x, "min"),
blockr::value(x, "max"),
blockr::value(x, "step")
)
}
registerS3method("ui_input", "slider_field", ui_input.slider_field)
registerS3method("ui_update", "slider_field", ui_update.slider_field)
new_slice_block <- function(from = 0, ...) {
n_rows <- \(data) nrow(data)
fields <- list(
rows = new_slider_field(
value = from,
min = 0,
max = n_rows,
step = 1,
title = "Select rows"
)
)
new_block(
fields = fields,
expr = quote(dplyr::slice(seq_len(.(rows)))),
name = "Slider slice block",
...,
class = c("slice_block", "transform_block")
)
}
serve_stack(
new_stack(
new_dataset_block("iris"),
new_slice_block(5)
)
)