Skip to content

Commit 03f4e38

Browse files
committed
Add input_check_buttons() and input_radio_buttons()
1 parent d7b50d9 commit 03f4e38

File tree

5 files changed

+235
-0
lines changed

5 files changed

+235
-0
lines changed

DESCRIPTION

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ Collate:
119119
'files.R'
120120
'fill.R'
121121
'imports.R'
122+
'input-button-group.R'
122123
'input-dark-mode.R'
123124
'input-switch.R'
124125
'layout.R'

NAMESPACE

+4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export(font_collection)
7373
export(font_face)
7474
export(font_google)
7575
export(font_link)
76+
export(input_check_buttons)
7677
export(input_dark_mode)
78+
export(input_radio_buttons)
7779
export(input_switch)
7880
export(is.card_item)
7981
export(is_bs_theme)
@@ -137,7 +139,9 @@ export(toggle_sidebar)
137139
export(toggle_switch)
138140
export(toggle_tooltip)
139141
export(tooltip)
142+
export(update_check_buttons)
140143
export(update_popover)
144+
export(update_radio_buttons)
141145
export(update_switch)
142146
export(update_tooltip)
143147
export(value_box)

R/input-button-group.R

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#' Create a button group of radio/check boxes
2+
#'
3+
#' Use `input_check_buttons()` if multiple choices may be selected at once; otherwise, use `input_radio_buttons()`
4+
#'
5+
#' @inheritParams input_check_search
6+
#' @param size size of the button group
7+
#' @param bg a theme color to use for the btn modifier class
8+
#' @export
9+
input_check_buttons <- function(id, choices, ..., selected = NULL, size = c("md", "sm", "lg"), bg = "primary") {
10+
size <- match.arg(size)
11+
tag <- div(
12+
id = id,
13+
class = "btn-group bslib-toggle-buttons",
14+
class = if (size != "md") paste0("btn-group-", size),
15+
role = "group",
16+
...,
17+
!!!input_buttons_container(
18+
type = "checkbox", id = id, choices = choices, selected = selected,
19+
size = size, bg = bg
20+
),
21+
toggle_dependency()
22+
)
23+
tag <- tag_require(tag, version = 5, caller = "input_check_buttons()")
24+
as_fragment(tag)
25+
}
26+
27+
#' @export
28+
#' @rdname input_check_buttons
29+
update_check_buttons <- function(id, choices = NULL, selected = NULL, session = get_current_session()) {
30+
if (!is.null(choices)) {
31+
choices <- processDeps(
32+
input_buttons_container(type = "checkbox", id, choices, selected),
33+
session
34+
)
35+
}
36+
message <- dropNulls(list(
37+
choices = choices,
38+
selected = as.list(selected)
39+
))
40+
session$sendInputMessage(id, message)
41+
}
42+
43+
#' @export
44+
#' @rdname input_check_buttons
45+
input_radio_buttons <- function(id, choices, ..., selected = NULL, size = c("md", "sm", "lg"), bg = "primary") {
46+
size <- match.arg(size)
47+
tag <- div(
48+
id = id,
49+
class = "btn-group bslib-toggle-buttons",
50+
class = if (size != "md") paste0("btn-group-", size),
51+
role = "group",
52+
...,
53+
!!!input_buttons_container(
54+
type = "radio", id = id, choices = choices, selected = selected,
55+
size = size, bg = bg
56+
),
57+
toggle_dependency()
58+
)
59+
tag <- tag_require(tag, version = 5, caller = "input_radio_buttons()")
60+
as_fragment(tag)
61+
}
62+
63+
#' @export
64+
#' @rdname input_check_buttons
65+
update_radio_buttons <- function(id, choices = NULL, selected = NULL, session = get_current_session()) {
66+
if (!is.null(choices)) {
67+
choices <- processDeps(
68+
input_buttons_container(type = "radio", id, choices, selected),
69+
session
70+
)
71+
}
72+
message <- dropNulls(list(
73+
choices = choices,
74+
selected = as.list(selected)
75+
))
76+
session$sendInputMessage(id, message)
77+
}
78+
79+
80+
input_buttons_container <- function(type = c("radio", "checkbox"), id, choices, selected, size = "md", bg = "primary") {
81+
82+
if (is.null(names(choices)) && is.atomic(choices)) {
83+
names(choices) <- choices
84+
}
85+
if (is.null(names(choices))) {
86+
stop("names() must be provided on list() vectors provided to choices")
87+
}
88+
89+
vals <- rlang::names2(choices)
90+
#if (!all(nzchar(vals))) {
91+
# stop("Input values must be non-empty character strings")
92+
#}
93+
94+
is_checked <- vapply(vals, function(x) isTRUE(x %in% selected) || identical(I("all"), selected), logical(1))
95+
96+
if (!any(is_checked) && !identical(selected, I("none"))) {
97+
is_checked[1] <- TRUE
98+
}
99+
100+
type <- match.arg(type)
101+
if (type == "radio" && sum(is_checked) > 1) {
102+
stop("input_radio_buttons() doesn't support more than one selected choice (do you want input_check_buttons() instead?)", call. = FALSE)
103+
}
104+
105+
inputs <- Map(
106+
vals, choices, is_checked, paste0(id, "-", seq_along(is_checked)),
107+
f = function(val, lbl, checked, this_id) {
108+
list(
109+
tags$input(
110+
type = type, class = "btn-check", name = id,
111+
id = this_id, autocomplete = "off",
112+
`data-value` = val,
113+
checked = if (checked) NA
114+
),
115+
tags$label(
116+
class = paste0("btn btn-outline-", bg),
117+
`for` = this_id, lbl
118+
)
119+
)
120+
}
121+
)
122+
123+
inputs <- unlist(inputs, recursive = FALSE, use.names = FALSE)
124+
}
125+
126+
toggle_dependency <- function() {
127+
htmltools::htmlDependency(
128+
"bslib-toggle-buttons",
129+
version = get_package_version("bslib"),
130+
package = "bslib",
131+
src = "components",
132+
script = "toggle-buttons.js"
133+
)
134+
}

inst/components/toggle-buttons.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
var toggleButtonsInputBinding = new Shiny.InputBinding();
2+
$.extend(toggleButtonsInputBinding, {
3+
4+
find: function(scope) {
5+
return $(scope).find(".btn-group.bslib-toggle-buttons");
6+
},
7+
8+
getValue: function(el) {
9+
var inputs = $(el).find("input.btn-check");
10+
var vals = [];
11+
inputs.each(function(i) {
12+
if (this.checked) {
13+
vals.push($(this).attr("data-value"));
14+
}
15+
});
16+
return vals.length > 0 ? vals : null;
17+
},
18+
19+
subscribe: function(el, callback) {
20+
$(el).on(
21+
'change.toggleButtonsInputBinding',
22+
function(event) { callback(true); }
23+
);
24+
},
25+
26+
unsubscribe: function(el) {
27+
$(el).off(".toggleButtonsInputBinding");
28+
},
29+
30+
receiveMessage: function(el, data) {
31+
if (data.hasOwnProperty("choices")) {
32+
Shiny.renderContent(el, data.choices);
33+
} else if (data.hasOwnProperty("selected")) {
34+
const inputs = $(el).find("input");
35+
inputs.each(function(i) {
36+
const val = $(this).attr("data-value");
37+
const checked = data.selected.indexOf(val) > -1;
38+
this.checked = checked;
39+
});
40+
}
41+
42+
$(el).trigger("change.toggleButtonsInputBinding");
43+
}
44+
45+
});
46+
47+
Shiny.inputBindings.register(toggleButtonsInputBinding);

man/input_check_buttons.Rd

+49
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)