Skip to content

Commit 10b8e68

Browse files
authored
feat: Create Session.set_message_handler(name, handler) (#1253)
1 parent 7c0ee6a commit 10b8e68

File tree

12 files changed

+188
-288
lines changed

12 files changed

+188
-288
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### New features
1717

18+
* `Session` objects now have a `set_message_handler(name, fn)` method that allows you to register a message handler function that will be called when a request message with the given name is received from the client (via `Shiny.shinyapp.makeRequest()` (JS)). (#1253)
19+
1820
* Experimental: `@render.data_frame` return values of `DataTable` and `DataGrid` support `mode="edit"` to enable editing of the data table cells. (#1198)
1921

2022
* `ui.card()` and `ui.value_box()` now take an `id` argument that, when provided, is used to report the full screen state of the card or value box to the server. For example, when using `ui.card(id = "my_card", full_screen = TRUE)` you can determine if the card is currently in full screen mode by reading the boolean value of `input.my_card()["full_screen"]`. (#1215)

js/dataframe/cell.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
getCellEditMapValue,
2121
} from "./cell-edit-map";
2222
import { updateCellsData } from "./data-update";
23+
import type { PatchInfo } from "./types";
2324

2425
// States
2526
// # √ Ready
@@ -50,6 +51,7 @@ export type CellState = keyof typeof CellStateEnum;
5051
interface TableBodyCellProps {
5152
id: string | null;
5253
cell: Cell<unknown[], unknown>;
54+
patchInfo: PatchInfo;
5355
columns: readonly string[];
5456
editCellsIsAllowed: boolean;
5557
editRowIndex: number | null;
@@ -66,6 +68,7 @@ interface TableBodyCellProps {
6668
export const TableBodyCell: FC<TableBodyCellProps> = ({
6769
id,
6870
cell,
71+
patchInfo,
6972
columns,
7073
editCellsIsAllowed,
7174
editRowIndex,
@@ -137,7 +140,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
137140
// * When a td is focused, Have esc key move focus to the table
138141
// * When table is focused, Have esc key blur the focus
139142
// TODO-barret-future; Combat edit mode being independent of selection mode
140-
// * In row / column selection mode, allow for arrowoutput_binding_request_handler key navigation by focusing on a single cell, not a TR
143+
// * In row / column selection mode, allow for arrow key navigation by focusing on a single cell, not a TR
141144
// * If a cell is focused,
142145
// * `enter key` allows you to go into edit mode; If editing is turned off, the selection is toggled
143146
// * `space key` allows you toggle the selection of the cell
@@ -253,6 +256,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
253256
// updateCellsData updates the underlying data via `setData` and `setCellEditMap`
254257
updateCellsData({
255258
id,
259+
patchInfo: patchInfo,
256260
patches: [
257261
{
258262
rowIndex,
@@ -273,6 +277,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
273277
});
274278
}, [
275279
id,
280+
patchInfo,
276281
rowIndex,
277282
columnIndex,
278283
value,

js/dataframe/data-update.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ResponseValue, makeRequestPromise } from "./request";
33
import type { CellState } from "./cell";
44
import { CellStateEnum } from "./cell";
55
import { CellEdit, SetCellEditMap, makeCellEditMapKey } from "./cell-edit-map";
6+
import type { PatchInfo } from "./types";
67

78
export type CellPatch = {
89
rowIndex: number;
@@ -19,6 +20,7 @@ export type CellPatchPy = {
1920

2021
export function updateCellsData({
2122
id,
23+
patchInfo,
2224
patches,
2325
onSuccess,
2426
onError,
@@ -27,6 +29,7 @@ export function updateCellsData({
2729
setCellEditMap,
2830
}: {
2931
id: string | null;
32+
patchInfo: PatchInfo;
3033
patches: CellPatch[];
3134
onSuccess: (values: CellPatch[]) => void;
3235
onError: (err: string) => void;
@@ -47,12 +50,8 @@ export function updateCellsData({
4750
});
4851

4952
makeRequestPromise({
50-
method: "output_binding_request_handler",
53+
method: patchInfo.key,
5154
args: [
52-
// id: string
53-
id,
54-
// handler: string
55-
"patches",
5655
// list[CellPatch]
5756
patchesPy,
5857
],

js/dataframe/index.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { SortArrow } from "./sort-arrows";
3737
import css from "./styles.scss";
3838
import { useTabindexGroup } from "./tabindex-group";
3939
import { useSummary } from "./table-summary";
40-
import { EditModeEnum, PandasData, TypeHint } from "./types";
40+
import { EditModeEnum, PandasData, PatchInfo, TypeHint } from "./types";
4141

4242
// TODO-barret; Type support
4343
// export interface PandasData<TIndex> {
@@ -87,11 +87,16 @@ declare module "@tanstack/table-core" {
8787
interface ShinyDataGridProps<TIndex> {
8888
id: string | null;
8989
data: PandasData<TIndex>;
90+
patchInfo: PatchInfo;
9091
bgcolor?: string;
9192
}
9293

93-
const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
94-
const { id, data, bgcolor } = props;
94+
const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
95+
id,
96+
data,
97+
patchInfo,
98+
bgcolor,
99+
}) => {
95100
const { columns, type_hints: typeHints, data: rowData } = data;
96101
const { width, height, fill, filters: withFilters } = data.options;
97102

@@ -458,6 +463,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
458463
id={id}
459464
key={cell.id}
460465
cell={cell}
466+
patchInfo={patchInfo}
461467
editCellsIsAllowed={editCellsIsAllowed}
462468
columns={columns}
463469
editRowIndex={editRowIndex}
@@ -645,10 +651,12 @@ export class ShinyDataFrameOutput extends HTMLElement {
645651
}
646652
}
647653

648-
renderValue(data: unknown) {
654+
renderValue(
655+
value: null | { patchInfo: PatchInfo; data: PandasData<unknown> }
656+
) {
649657
this.clearError();
650658

651-
if (!data) {
659+
if (!value) {
652660
this.reactRoot!.render(null);
653661
return;
654662
}
@@ -657,7 +665,8 @@ export class ShinyDataFrameOutput extends HTMLElement {
657665
<StrictMode>
658666
<ShinyDataGrid
659667
id={this.id}
660-
data={data as PandasData<unknown>}
668+
data={value.data}
669+
patchInfo={value.patchInfo}
661670
bgcolor={getComputedBgColor(this)}
662671
></ShinyDataGrid>
663672
</StrictMode>

js/dataframe/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ export interface PandasData<TIndex> {
3434
type_hints?: ReadonlyArray<TypeHint>;
3535
options: DataGridOptions;
3636
}
37+
38+
export interface PatchInfo {
39+
key: string;
40+
}

shiny/render/_dataframe.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from typing import (
66
TYPE_CHECKING,
77
Any,
8+
Awaitable,
9+
Callable,
810
Literal,
911
Optional,
1012
Protocol,
@@ -23,7 +25,7 @@
2325
from .._typing_extensions import Self
2426
from ..session._utils import get_current_session, require_active_session
2527
from ._dataframe_unsafe import serialize_numpy_dtypes
26-
from .renderer import Jsonifiable, Renderer, ValueFn, output_binding_request_handler
28+
from .renderer import Jsonifiable, Renderer, ValueFn
2729
from .renderer._utils import JsonifiableDict
2830

2931
if TYPE_CHECKING:
@@ -618,19 +620,31 @@ async def patches_fn(
618620

619621
self.set_patch_fn(patch_fn)
620622
self.set_patches_fn(patches_fn)
621-
# self._add_message_handlers()
622623

623-
# # TODO-barret; Use dynamic route instead of message handlers? Gut all of `output_binding_request_handler?` TODO: Talk with Joe
624-
# session = get_current_session()
625-
# if session is not None:
626-
# session.dynamic_route("data-frame-patches-{id}", self._handle_patches)
624+
def _set_patches_handler_impl(
625+
self,
626+
handler: Callable[..., Awaitable[Jsonifiable]] | None,
627+
) -> str:
628+
session = self._get_session()
629+
key = session.set_message_handler(
630+
f"data_frame_patches_{self.output_id}",
631+
handler,
632+
)
633+
return key
634+
635+
def _reset_patches_handler(self) -> str:
636+
return self._set_patches_handler_impl(None)
627637

628-
# output_binding_request_handler(session, "name", self._handle_patches_1)
638+
def _set_patches_handler(self) -> str:
639+
"""
640+
Set the client patches handler for the data frame.
641+
642+
This method **must be** called as late as possible as it depends on the ID of the output.
643+
"""
644+
return self._set_patches_handler_impl(self._patches_handler)
629645

630-
# To be called by session's output_binding_request_handler message handler on this data_frame instance
631-
@output_binding_request_handler
632646
# Do not change this method name unless you update corresponding code in `/js/dataframe/`!!
633-
async def _handle_patches(self, patches: list[CellPatch]) -> Jsonifiable:
647+
async def _patches_handler(self, patches: list[CellPatch]) -> Jsonifiable:
634648
# TODO-barret; verify that the patches are in the correct format
635649

636650
# Call user's cell update method to retrieve formatted values
@@ -713,6 +727,7 @@ def _set_output_metadata(self, *, output_id: str) -> None:
713727
async def render(self) -> Jsonifiable:
714728
# Reset value
715729
self._reset_reactives()
730+
self._reset_patches_handler()
716731

717732
value = await self.fn()
718733
if value is None:
@@ -726,8 +741,15 @@ async def render(self) -> Jsonifiable:
726741
)
727742
)
728743

744+
# Send info to client
745+
patch_key = self._set_patches_handler()
729746
self._value.set(value)
730-
return value.to_payload()
747+
return {
748+
"data": value.to_payload(),
749+
"patchInfo": {
750+
"key": patch_key,
751+
},
752+
}
731753

732754
async def _send_message_to_browser(self, handler: str, obj: dict[str, Any]):
733755

shiny/render/renderer/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from ._dispatch import output_binding_request_handler
2-
from ._renderer import ( # noqa: F401
1+
from ._renderer import (
32
AsyncValueFn,
43
Jsonifiable,
54
Renderer,
@@ -13,5 +12,4 @@
1312
"Jsonifiable",
1413
"AsyncValueFn",
1514
"RendererT",
16-
"output_binding_request_handler",
1715
)

shiny/render/renderer/_dispatch.py

Lines changed: 0 additions & 84 deletions
This file was deleted.

0 commit comments

Comments
 (0)