Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions shiny/api-examples/bookmark_on_session_disconnect/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import textwrap

from starlette.requests import Request

from shiny import App, Inputs, Outputs, Session, reactive, render, ui
from shiny.bookmark import BookmarkState


# App UI **must** be a function to ensure that each user restores their own UI values.
def app_ui(request: Request):
return ui.page_fluid(
ui.markdown(
"Directions: "
"\n1. Change the radio buttons below"
"\n2. Refresh your browser."
"\n3. The radio buttons should be restored to their previous state."
"\n4. Check the console messages for bookmarking events."
),
ui.hr(),
ui.input_radio_buttons(
"letter",
"Choose a letter (Store in Bookmark 'input')",
choices=["A", "B", "C"],
),
"Selection:",
ui.output_code("letter_out"),
ui.input_action_button("stop_session", "Stop session"),
ui.tags.script(
textwrap.dedent(
"""
{
console.log("adding shiny:connected and shiny:disconnected event listeners");
let latest_url = null;
// window.document.onshiny:disconnected
$(document).on("shiny:connected", function(event) {
// The session has ended, so we can update the URL with the bookmark URL
console.log("Session connected, setting latest_url to:", event);
});
$(document).on("shiny:disconnected", function(event) {
// The session has ended, so we can update the URL with the bookmark URL
console.log("Session disconnected, setting latest_url to:", latest_url);
// window.location = latest_url;
window.history.replaceState(null, null, latest_url);

});

Shiny.addCustomMessageHandler("updateUrlOnSessionDisconnect", function(message) {
// Update the URL with the bookmark URL
console.log("Received latest bookmark URL:", message.url);
latest_url = message.url;
})
}
"""
)
),
)


def server(input: Inputs, output: Outputs, session: Session):

@reactive.effect
@reactive.event(input.stop_session)
async def _():
print("Stopping session...")
await session.stop()

@render.code
def letter_out():
return str(input.letter())

# When the user interacts with the input, we will bookmark the state.
@reactive.effect
@reactive.event(input.letter, ignore_init=True)
async def _():
await session.bookmark()

# After saving state, we will update the query string with the bookmark URL.
@session.bookmark.on_bookmarked
async def _(url: str):
print("Session end url:", url)
await session.bookmark.update_on_session_disconnect(url)


# Make sure to set the bookmark_store to `"url"` (or `"server"`)
# to store the bookmark information/key in the URL query string.
app = App(app_ui, server, bookmark_store="url")
31 changes: 31 additions & 0 deletions shiny/bookmark/_bookmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,21 @@ async def update_query_string(
"""
...

@abstractmethod
async def update_on_session_disconnect(self, url: Optional[str] = None) -> None:
"""
Update the query string of the current URL when the session ends/disconnects.

This is used to set the URL _only_ when the user's session disconnects from the
server.

Parameters
----------
url
The URL to set. If `None`, the current bookmark state URL will be used.
"""
...

@abstractmethod
async def get_bookmark_url(self) -> str | None:
"""
Expand Down Expand Up @@ -465,6 +480,15 @@ async def update_query_string(
}
)

async def update_on_session_disconnect(self, url: Optional[str] = None) -> None:
if url is None:
url = await self.get_bookmark_url()

await self._root_session.send_custom_message(
"updateUrlOnSessionDisconnect",
{"url": url},
)

def _get_bookmark_exclude(self) -> list[str]:
"""
Get the list of inputs excluded from being bookmarked.
Expand Down Expand Up @@ -732,6 +756,9 @@ async def update_query_string(
) -> None:
await self._root_bookmark.update_query_string(query_string, mode)

async def update_on_session_disconnect(self, url: Optional[str] = None) -> None:
await self._root_bookmark.update_on_session_disconnect(url)

async def get_bookmark_url(self) -> str | None:
return await self._root_bookmark.get_bookmark_url()

Expand Down Expand Up @@ -781,6 +808,10 @@ async def update_query_string(
# no-op within ExpressStub
return None

async def update_on_session_disconnect(self, url: Optional[str] = None) -> None:
# no-op within ExpressStub
return None

async def get_bookmark_url(self) -> str | None:
return None

Expand Down
Loading