Skip to content

Commit

Permalink
Runs onload handler for browser navigations (backwards and forwards) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwillchen authored Nov 13, 2024
1 parent 38edea8 commit 04995df
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 14 deletions.
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 @@ -210,9 +210,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 @@ -240,14 +249,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 @@ -265,6 +267,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

0 comments on commit 04995df

Please sign in to comment.