Skip to content

Commit c5740e6

Browse files
committed
Improved 2-way communication. New function gt_update_select() can be used to update the selection in a gt table in Shiny. The gt input also now differentiates between "nothing is selected because this table has just initialized" and "nothing is selected because the user deselected everything," which is very useful for updating related inputs and avoiding loops.
I think this is done. Let me know if you'd like more tests or examples (or anything else)! I'd love to use this, so let me know what I need to do to help push it over the goal line!
1 parent 007ac24 commit c5740e6

File tree

6 files changed

+161
-6
lines changed

6 files changed

+161
-6
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export(gt_latex_dependencies)
157157
export(gt_output)
158158
export(gt_preview)
159159
export(gt_split)
160+
export(gt_update_select)
160161
export(gtsave)
161162
export(html)
162163
export(info_currencies)

R/shiny.R

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@
3636
#' holding the **gt** table. The `width` and `height` arguments allow for sizing
3737
#' the container, and the `align` argument allows us to align the table within
3838
#' the container (some other fine-grained options for positioning are available
39-
#' in [tab_options()]).
39+
#' in [tab_options()]). If the table is interactive, the selected row indices
40+
#' (relative to the underlying data, regardless of sorting) are available as
41+
#' `input$id`, where `id` is the `outputId` used for this table in [gt_output()].
42+
#' If the user has deselected all rows, the value is `0` (vs `NULL` when the
43+
#' table initializes).
4044
#'
4145
#' @param expr *Expression*
4246
#'
@@ -287,4 +291,44 @@ gt_output <- function(outputId) {
287291
shiny::htmlOutput(outputId, class = "gt_shiny")
288292
}
289293

294+
# gt_update_select() -----------------------------------------------------------
295+
#' Update a **gt** selection in Shiny
296+
#'
297+
#' @description
298+
#'
299+
#' Update the selection in an interactive **gt** table rendered using
300+
#' [render_gt()]. The table must be interactive and have selection enabled (see
301+
#' [opt_interactive()]).
302+
#'
303+
#' @param outputId *Shiny output ID*
304+
#'
305+
#' `scalar<character>` // **required**
306+
#'
307+
#' The id of the [gt_output()] element to update.
308+
#' @param rows *Row indices*
309+
#'
310+
#' `<integer>` // **required**
311+
#'
312+
#' The id of the [gt_output()] element to update.
313+
#' @param session *Shiny session*
314+
#'
315+
#' `scalar<ShinySession>` // **required**
316+
#'
317+
#' The session in which the [gt_output()] element can be found. You almost
318+
#' certainly want to leave this as the default value.
319+
#'
320+
#' @return A call to the JavaScript binding of the table.
321+
#' @family Shiny functions
322+
#' @section Function ID:
323+
#' 12-3
324+
#'
325+
#' @export
326+
#'
327+
#' @examples
328+
gt_update_select <- function(outputId,
329+
rows,
330+
session = shiny::getDefaultReactiveDomain()) {
331+
session$sendInputMessage(outputId, rows - 1)
332+
}
333+
290334
#nocov end

inst/shiny/gtShiny.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,32 @@ $.extend(gtShinyBinding, {
2525
return;
2626
}
2727
var self = this;
28+
el.__clickFlag = false;
29+
2830
var observer = new MutationObserver(function(mutations, obs) {
2931
var reactableElement = self.getReactable(el);
3032
if (reactableElement) {
3133
var rows = reactableElement.querySelectorAll('.rt-tr');
3234
if (rows.length > 0) {
35+
if (!reactableElement.__clickListenerAdded) {
36+
reactableElement.addEventListener('click', function() {
37+
el.__clickFlag = true;
38+
});
39+
reactableElement.__clickListenerAdded = true;
40+
}
41+
3342
if (!reactableElement.__reactableStateChangeListener) {
3443
reactableElement.__reactableStateChangeListener = function() {
3544
$(el).trigger('change.gtShiny');
3645
};
3746
Reactable.onStateChange(reactableElement.id, reactableElement.__reactableStateChangeListener);
3847
}
39-
$(el).trigger('change.gtShiny');
4048
el.__initialized = true;
49+
if (el.__awaiting_set && el.__awaiting_set.length) {
50+
value = el.__awaiting_set;
51+
el.__awaiting_set = null;
52+
self.setValue(el, value);
53+
}
4154
obs.disconnect();
4255
}
4356
}
@@ -75,15 +88,50 @@ $.extend(gtShinyBinding, {
7588
getValue: function(el) {
7689
if (!el.__initialized) {
7790
this.initializeListener(el);
78-
return null;
91+
return; // Table is reloading or not fully initialized
7992
}
8093
var reactableElement = this.getReactable(el);
8194
if (reactableElement) {
8295
var selectedRows = Reactable.getState(reactableElement.id).selected;
83-
return selectedRows ? selectedRows.map(function(row) { return row + 1; }) : null;
96+
if (selectedRows === undefined) {
97+
return null;
98+
} else if (selectedRows.length === 0) {
99+
if (el.__clickFlag) {
100+
el.__clickFlag = false;
101+
return [0]; // [0] if nothing is selected due to user click
102+
} else {
103+
return null; // null if table is initializing or reloading
104+
}
105+
} else {
106+
el.__clickFlag = false;
107+
return selectedRows.map(function(row) { return row + 1; });
108+
}
84109
}
85110
return null;
86111
},
112+
/**
113+
* Sets the value of the selected rows in the gtShiny element.
114+
*
115+
* @param {HTMLElement} el - The element containing the gtShiny.
116+
* @param {Array} value - The row IDs to set as selected.
117+
*/
118+
setValue: function(el, value) {
119+
if (!Array.isArray(value)) {
120+
value = [value];
121+
}
122+
if (!el.__initialized) {
123+
el.__awaiting_set = value;
124+
this.initializeListener(el);
125+
return;
126+
}
127+
var reactableElement = this.getReactable(el);
128+
if (reactableElement) {
129+
var instance = Reactable.getInstance(reactableElement.id);
130+
if (instance) {
131+
instance.setRowsSelected(value);
132+
}
133+
}
134+
},
87135
/**
88136
* Update Shiny when a gtShiny element changes.
89137
*
@@ -103,6 +151,15 @@ $.extend(gtShinyBinding, {
103151
*/
104152
unsubscribe: function(el) {
105153
$(el).off('change.gtShiny');
154+
},
155+
/**
156+
* Receives a message from Shiny and sets the selected rows.
157+
*
158+
* @param {HTMLElement} el - The element containing the gtShiny.
159+
* @param {Array} value - The row IDs to set as selected.
160+
*/
161+
receiveMessage: function(el, value){
162+
this.setValue(el, value);
106163
}
107164
});
108165

man/gt_output.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/gt_update_select.Rd

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/render_gt.Rd

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)