Add @js_action decorator for client-side open/close/toggle optimization#5843
Draft
evnchn wants to merge 6 commits intozauberzeug:mainfrom
Draft
Add @js_action decorator for client-side open/close/toggle optimization#5843evnchn wants to merge 6 commits intozauberzeug:mainfrom
evnchn wants to merge 6 commits intozauberzeug:mainfrom
Conversation
Collaborator
Author
|
Human note: This is either 3.10 or 3.11, so this will take a while. Let's slow-cook this. Meanwhile, it is a sneak peak as to what could be done. I would have went for evnchn/nicegui treatment, but I need to show this for the SEO context: This PR would supersede the hack in #5801 |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The js_action JS handlers set element props and invalidate the vnode cache, but Vue's reactivity doesn't trigger a re-render because the cached render path short-circuits before reading element props, so the dependency is never tracked. Adding $forceUpdate() ensures Vue picks up the prop change after cache invalidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Set LOOPBACK = False so backdrop/outside clicks update model-value directly on the client without a server round-trip. Also fix the client-side loopback handler: unwrap single-element args arrays and call $forceUpdate() to ensure Vue re-renders after cache invalidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add type: ignore for update_wrapper on _JsActionDescriptor - Add type: ignore for JsAction callbacks passed to on() in fab/dropdown - Replace ValueElement import with hasattr check to break cyclic import (nicegui.element -> nicegui.js_action -> nicegui.elements.mixins.value_element) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add type: ignore for JsAction callbacks passed to on() in button, chip, and item elements. The JsAction handler type doesn't match GenericEventArguments but works correctly at runtime since it ignores event arguments entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use proper typing instead of suppressing mypy errors: - Add JsAction to Element.on() handler type since it already handles it at runtime - Use TypeGuard for has_js_action() to enable mypy type narrowing - Replace functools.update_wrapper with manual attribute copying to avoid arg-type mismatch - Use explicit _JsActionDescriptor return types instead of TypeVar cast Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Motivation
When using
on_click=dialog.openor similar patterns, the action currently requires a full server round-trip before the UI updates. This introduces noticeable latency, especially on slower connections. By running a JavaScript equivalent on the client side in addition to the server-side handler, the UI responds instantly while server state stays in sync.Implementation
Introduces a
@js_actiondecorator (nicegui/js_action.py) that marks element methods (likeopen,close,toggle) as having a client-side JavaScript equivalent:JsActionwrapper: When a decorated method is accessed on an element instance, it returns aJsActionobject that carries both the Python callable and a JS handler string.Element.on()integration: When aJsActionis passed as a handler,Element.on()automatically installs the JS handler for instant client-side feedback and suppresses the redundant server→client update (loopback) since the client already applied the change.js_action.value(v)/js_action.toggle(): Convenience constructors for the common patterns of setting or toggling amodel-valueprop.has_js_action(): Utility to detectJsActionhandlers, used by elements with customon_clickwrappers (Button,Chip,Item,FabAction,DropdownButton) to pass the handler directly toon()instead of wrapping it in a lambda.dialog.open()still work identically. Subclasses that override decorated methods lose the JS action (by design), so custom logic always runs server-side.Applied
@js_actionto:Dialog.open/close,Menu.open/close/toggle,DropdownButton.open/close/toggle,Fab.open/close/toggle.Progress
ui.dialogto close it still causes server involvement