Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runs onload handler for browser navigations (backwards and forwards) #1101

Merged
merged 3 commits into from
Nov 13, 2024
Merged
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
3 changes: 3 additions & 0 deletions mesop/examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
boilerplate_free_event_handlers as boilerplate_free_event_handlers,
)
from mesop.examples import box as box
from mesop.examples import (
browser_navigation_on_load as browser_navigation_on_load,
)
from mesop.examples import buttons as buttons
from mesop.examples import checkbox_and_radio as checkbox_and_radio
from mesop.examples import composite as composite
Expand Down
32 changes: 32 additions & 0 deletions mesop/examples/browser_navigation_on_load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import mesop as me


@me.stateclass
class State:
page_1_onload_counter: int
page_2_onload_counter: int


def page_1_on_load(e: me.LoadEvent):
me.state(State).page_1_onload_counter += 1


@me.page(path="/browser_navigation_on_load/page1", on_load=page_1_on_load)
def page1():
me.text("page1")
me.text(f"onload ran {me.state(State).page_1_onload_counter} times")
me.button("navigate", on_click=navigate)


def navigate(e: me.ClickEvent):
me.navigate("/browser_navigation_on_load/page2")


def page_2_on_load(e: me.LoadEvent):
me.state(State).page_2_onload_counter += 1


@me.page(path="/browser_navigation_on_load/page2", on_load=page_2_on_load)
def page2():
me.text("page2")
me.text(f"onload ran {me.state(State).page_2_onload_counter} times")
9 changes: 7 additions & 2 deletions mesop/protos/ui.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ message QueryParam {
repeated string values = 2;
}

// Next ID: 19
// Next ID: 20
message UserEvent {
optional States states = 1;

Expand All @@ -43,6 +43,7 @@ message UserEvent {
double double_value = 7;
int32 int_value = 8;
NavigationEvent navigation = 6;
HotReloadEvent hot_reload = 19;
ResizeEvent resize = 10;
bytes bytes_value = 9;
ChangePrefersColorScheme change_prefers_color_scheme = 14;
Expand Down Expand Up @@ -107,10 +108,14 @@ message DateRangePickerEvent {
optional string end_date = 2;
}

// This is a user-triggered navigation (e.g. go back/forwards) or a hot reload event.
// This is a user-triggered navigation (e.g. go back/forwards).
message NavigationEvent{
}

// Fired when a hot reload is triggered.
message HotReloadEvent {
}

// Fired whenever a user resizes the viewport/browser.
message ResizeEvent {
}
Expand Down
1 change: 1 addition & 0 deletions mesop/runtime/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def run_event_handler(
) -> Generator[None, None, None]:
if (
event.HasField("navigation")
or event.HasField("hot_reload")
or event.HasField("resize")
or event.HasField("change_prefers_color_scheme")
):
Expand Down
32 changes: 23 additions & 9 deletions mesop/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,18 @@ def generate_data(ui_request: pb.UiRequest) -> Generator[str, None, None]:
runtime().context().reset_previous_node()
runtime().context().reset_current_node()

result = runtime().context().run_event_handler(ui_request.user_event)
path = ui_request.path
has_run_navigate_on_load = False

if ui_request.user_event.HasField("navigation"):
page_config = runtime().get_page_config(path=path)
if (
page_config and page_config.on_load and not has_run_navigate_on_load
):
has_run_navigate_on_load = True
yield from run_page_load(path=path)

result = runtime().context().run_event_handler(ui_request.user_event)
for _ in result:
navigate_commands = [
command
Expand Down Expand Up @@ -223,14 +232,7 @@ def generate_data(ui_request: pb.UiRequest) -> Generator[str, None, None]:
and not has_run_navigate_on_load
):
has_run_navigate_on_load = True
result = page_config.on_load(LoadEvent(path=path))
# on_load is a generator function then we need to iterate through
# the generator object.
if result:
for _ in result:
yield from render_loop(path=path, init_request=True)
runtime().context().set_previous_node_from_current_node()
runtime().context().reset_current_node()
yield from run_page_load(path=path)

yield from render_loop(path=path)
runtime().context().set_previous_node_from_current_node()
Expand All @@ -248,6 +250,18 @@ def generate_data(ui_request: pb.UiRequest) -> Generator[str, None, None]:
error=pb.ServerError(exception=str(e), traceback=format_traceback())
)

def run_page_load(*, path: str):
page_config = runtime().get_page_config(path=path)
assert page_config and page_config.on_load
result = page_config.on_load(LoadEvent(path=path))
# on_load is a generator function then we need to iterate through
# the generator object.
if result:
for _ in result:
yield from render_loop(path=path, init_request=True)
runtime().context().set_previous_node_from_current_node()
runtime().context().reset_current_node()

@flask_app.route(UI_PATH, methods=["POST"])
def ui_stream() -> Response:
# Prevent CSRF by checking the request site matches the site
Expand Down
25 changes: 25 additions & 0 deletions mesop/tests/e2e/browser_navigation_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {test, expect} from '@playwright/test';

test('browser navigation - back and forward (triggers onload)', async ({
page,
}) => {
await page.goto('/browser_navigation_on_load/page1');
await expect(page.getByText('page1')).toBeVisible();
await expect(page.getByText('onload ran 1 times')).toBeVisible();

// Trigger a navigation.
await page.getByRole('button', {name: 'navigate'}).click();

await expect(page.getByText('page2')).toBeVisible();
await expect(page.getByText('onload ran 1 times')).toBeVisible();

// Go back to page 1
await page.goBack();
await expect(page.getByText('page1')).toBeVisible();
await expect(page.getByText('onload ran 2 times')).toBeVisible();

// Go forward to page 2
await page.goForward();
await expect(page.getByText('page2')).toBeVisible();
await expect(page.getByText('onload ran 2 times')).toBeVisible();
});
5 changes: 2 additions & 3 deletions mesop/web/src/services/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
UserEvent,
Component as ComponentProto,
UiResponse,
NavigationEvent,
ComponentConfig,
Command,
ChangePrefersColorScheme,
HotReloadEvent,
} from 'mesop/mesop/protos/ui_jspb_proto_pb/mesop/protos/ui_pb';
import {Logger} from '../dev_tools/services/logger';
import {Title} from '@angular/platform-browser';
Expand Down Expand Up @@ -482,8 +482,7 @@ export class Channel {
const request = new UiRequest();
const userEvent = new UserEvent();
userEvent.setStates(this.states);
const navigationEvent = new NavigationEvent();
userEvent.setNavigation(navigationEvent);
userEvent.setHotReload(new HotReloadEvent());
userEvent.setViewportSize(getViewportSize());
userEvent.setThemeSettings(this.themeService.getThemeSettings());
userEvent.setQueryParamsList(getQueryParams());
Expand Down
Loading