-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'upstream/main' into DH-18538_plotly-web…
…gl-fix
- Loading branch information
Showing
69 changed files
with
4,076 additions
and
154 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+608 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/iris_dashboard_column.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+921 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/iris_dashboard_row.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+150 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/iris_dashboard_stack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+749 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/iris_species_dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+981 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/iris_species_dashboard_final.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+955 KB
...ns/ui/docs/_assets/deephaven-ui-crash-course/iris_species_dashboard_resized.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+133 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/scatter_by_species.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+406 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/sepal_flex_column.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+57.1 KB
plugins/ui/docs/_assets/deephaven-ui-crash-course/species_picker_panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+58.7 KB
...s/ui/docs/_assets/deephaven-ui-crash-course/species_picker_panel_controlled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+79.4 KB
.../ui/docs/_assets/deephaven-ui-crash-course/species_picker_panel_illustrated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+92.2 KB
.../ui/docs/_assets/deephaven-ui-crash-course/species_picker_panel_investigate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# Render Cycle | ||
|
||
Before your components are displayed on screen, they must be rendered. Understanding the steps in this process will help you think about how your code executes and explain its behavior. | ||
|
||
Think of your components as chefs in a kitchen, preparing delicious meals from various ingredients. In this analogy, `deephaven.ui` acts as the waiter, taking orders from customers and delivering the finished dishes. This process of handling UI requests and rendering them involves three main steps: | ||
|
||
1. Triggering a render (delivering the guest’s order to the kitchen) | ||
2. Rendering the component (preparing the order in the kitchen) | ||
3. Committing to the DOM (placing the order on the table) | ||
|
||
## Step 1: Trigger a render | ||
|
||
There are two reasons for a component to render: | ||
|
||
1. It is the component’s initial render. | ||
2. The component’s state has been updated or one of it's ancestor's state has been updated. | ||
|
||
### Initial Render | ||
|
||
Opening a component to view it causes the component to be mounted, which means adding nodes to the DOM. When your component first mounts, it triggers an initial render. It is rendered with it's props and initial state. | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def example_renderer(): | ||
text = "Initial Render" | ||
return ui.text(text) | ||
|
||
|
||
example = example_renderer() | ||
``` | ||
|
||
### Re-renders when state updates | ||
|
||
Once the component has been initially rendered, you can trigger further renders by updating its state with the set function. Updating your component’s state automatically queues a render. (You can imagine these as a restaurant guest ordering tea, dessert, and all sorts of things after putting in their first order, depending on the state of their thirst or hunger.) | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def example_renderer(): | ||
num, set_num = ui.use_state(0) | ||
|
||
def handle_press(): | ||
set_num(num + 1) | ||
|
||
text = "Initial Render" if num == 0 else f"Re-render {num}" | ||
|
||
return [ | ||
ui.button("Render", on_press=handle_press), | ||
ui.text(text), | ||
] | ||
|
||
|
||
example = example_renderer() | ||
``` | ||
|
||
## Step 2: `deephaven.ui` renders your components | ||
|
||
After you trigger a render, `deephaven.ui` runs the component functions and then encodes your components as JSON which is sent from the server to the web client UI. The client decodes the JSON into a document which is rendered by React. | ||
|
||
This process is recursive: if the updated component returns some other component, `deephaven.ui` will render that component next, and if that component also returns something, it will render that component next, and so on. The process will continue until there are no more nested components. | ||
|
||
Rendering must always be a [pure](../describing/pure_components.md) calculation: | ||
|
||
- Same inputs, same output. Given the same inputs, a component should always return the output. | ||
- It minds its own business. It should not change any objects or variables that existed before rendering. | ||
|
||
Otherwise, you can encounter confusing bugs and unpredictable behavior as your codebase grows in complexity. | ||
|
||
### Step 3: Commit changes to the DOM | ||
|
||
After rendering your components, `deephaven.ui` sends the components to client and React renders and will modify the DOM. | ||
|
||
-For the initial render, React will use the `appendChild()` DOM API to put all the DOM nodes it has created on screen. | ||
-For re-renders, React will apply the minimal necessary operations (calculated while rendering!) to make the DOM match the latest rendering output. | ||
|
||
React only changes the DOM nodes if there’s a difference between renders. For example, here is a component that re-renders with different props passed from its parent every second. Notice how you can add some text into the `ui.text_field`, updating its value, but the text doesn’t disappear when the component re-renders: | ||
|
||
```python | ||
import time, threading | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def clock(t): | ||
return [ui.heading(t), ui.text_field()] | ||
|
||
|
||
@ui.component | ||
def clock_wrapper(): | ||
clock_time, set_clock_time = ui.use_state(time.ctime()) | ||
is_cancelled = False | ||
|
||
def periodic_update(): | ||
if is_cancelled: | ||
return | ||
set_clock_time(time.ctime()) | ||
threading.Timer(1, periodic_update).start() | ||
|
||
def start_update(): | ||
periodic_update() | ||
|
||
def cancel_timer(): | ||
nonlocal is_cancelled | ||
is_cancelled = True | ||
|
||
return cancel_timer | ||
|
||
start_timer = ui.use_callback(start_update, [set_clock_time]) | ||
ui.use_effect(start_timer, []) | ||
|
||
return clock(clock_time) | ||
|
||
|
||
clock_example = clock_wrapper() | ||
``` | ||
|
||
This works because during this last step, React only updates the content of `ui.header` with the new time. It sees that the `ui.text_field` appears in the JSX in the same place as last time, so React doesn’t touch the `ui.text_field` or its value. | ||
|
||
After rendering is done and React updated the DOM, the browser will repaint the screen. |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
# Respond to Events | ||
|
||
`deephaven.ui` lets you add event handlers to your components. Event handlers are your own functions that will be triggered in response to interactions like clicking, hovering, focusing form inputs, and so on. | ||
|
||
## Add event handlers | ||
|
||
To add an event handler, first define a function and then pass it as a prop to the appropriate component. For example, here is a button that doesn’t do anything yet: | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def my_button(): | ||
return ui.button("I don't do anything") | ||
|
||
|
||
no_button_event = my_button() | ||
``` | ||
|
||
You can make it print a message when a user clicks by following these three steps: | ||
|
||
1. Declare a function called `handle_press`. | ||
2. Implement the logic inside that function. | ||
3. Add `on_press=handle_press` to the button component. | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def my_button(): | ||
def handle_press(): | ||
print("You clicked me!") | ||
|
||
return ui.button("Click me", on_press=handle_press) | ||
|
||
|
||
button_with_event = my_button() | ||
``` | ||
|
||
You defined the `handle_press` function and then passed it as a prop to `ui.button`. `handle_press` is an event handler. Event handler functions: | ||
|
||
- Are usually defined inside your components. | ||
- Have names that start with handle, followed by the name of the event. | ||
|
||
By convention, it is common to name event handlers as "handle" followed by the event name. You’ll often see `on_press=handle_press`, `on_mouse_enter=handle_mouse_enter`, and so on. | ||
|
||
Alternatively, you can define an event handler inline with a lambda in the component: | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def my_button(): | ||
return ui.button("Click me", on_press=lambda: print("You clicked me!")) | ||
|
||
|
||
button_with_inline_event = my_button() | ||
``` | ||
|
||
These styles are equivalent. Inline event handlers are convenient for short functions. | ||
|
||
## Functions must be passed, not called | ||
|
||
Functions passed to event handlers must be passed, not called. For example: | ||
|
||
| Passing a function (correct) | Calling a function (incorrect) | | ||
| ---------------------------------- | ------------------------------------------------ | | ||
| `ui.button(on_press=handle_press)` | `ui.button("Click me", on_press=handle_press())` | | ||
|
||
The difference is subtle. In the first example, the `handle_press` function is passed as an `on_press` event handler. This tells `deephaven.ui` to remember it and only call your function when the user clicks the button. | ||
|
||
In the second example, the `()` at the end of `handle_press()` fires the function immediately during rendering, without any clicks. | ||
|
||
When you write code inline, the same pitfall presents itself in a different way: | ||
|
||
| Passing a function (correct) | Calling a function (incorrect) | | ||
| -------------------------------------------- | --------------------------------------------------------- | | ||
| `ui.button(on_press=lambda: print("click"))` | `ui.button("Click me", on_press=on_press=print("click"))` | | ||
|
||
The first example uses lambda to create an anonymous function that is called every time the button is clicked. | ||
|
||
The second example will execute the code every time the component renders. | ||
|
||
In both cases, you should pass a function: | ||
|
||
- `ui.button(on_press=handle_press)` passes the `handle_press` function. | ||
- `ui.button(on_press=lambda: print("click"))` passes the `lambda: print("click")` function. | ||
|
||
## Read props in event handlers | ||
|
||
Because event handlers are declared inside of a component, they have access to the component’s props. Here is a button that, when clicked, prints its message prop: | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def custom_button(label, message): | ||
return ui.button(label, on_press=lambda: print(message)) | ||
|
||
|
||
@ui.component | ||
def toolbar(): | ||
return [ | ||
custom_button("Play Movie", "Playing!"), | ||
custom_button("Upload Image", "Uploading!"), | ||
] | ||
|
||
|
||
read_props_example = toolbar() | ||
``` | ||
|
||
This lets these two buttons show different messages. Try changing the messages passed to them. | ||
|
||
## Pass event handlers as props | ||
|
||
Often, you’ll want the parent component to specify a child’s event handler. Consider buttons: depending on where you’re using a button component, you might want to execute a different function — perhaps one plays a movie and another uploads an image. | ||
|
||
To do this, pass a prop the component receives from its parent as the event handler like so: | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def custom_button(*children, on_press): | ||
return ui.button(children, on_press=on_press) | ||
|
||
|
||
@ui.component | ||
def play_button(movie_name): | ||
def handle_play_press(): | ||
print(f"Playing {movie_name}") | ||
|
||
return custom_button(f"Play {movie_name}", on_press=handle_play_press) | ||
|
||
|
||
@ui.component | ||
def upload_button(): | ||
return custom_button("Upload Image", on_press=lambda: print("Uploading!")) | ||
|
||
|
||
@ui.component | ||
def toolbar(): | ||
return [play_button("Alice in Wonderland"), upload_button()] | ||
|
||
|
||
pass_event_handlers = toolbar() | ||
``` | ||
|
||
Here, the `toolbar` component renders a `play_button` and an `upload_button`: | ||
|
||
- `play_button` passes `handle_play_press` as the `on_press` prop to the `custom_button` inside. | ||
- `upload_button` passes `lambda: print("Uploading!")` as the `on_press` prop to the `custom_button` inside. | ||
|
||
Finally, `custom_button` component accepts a prop called `on_press`. It passes that prop directly to the `ui.button` with `on_press=on_press`. This tells `deephaven.ui` to call the passed function on press. | ||
|
||
## Name event handler props | ||
|
||
When building your own components, you can name their event handler props any way that you like. | ||
|
||
By convention, event handler props should start with `on`, followed by an underscore. | ||
|
||
For example, the `custom_button` component’s `on_press` prop could have been called `on_smash`: | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def custom_button(*children, on_smash): | ||
return ui.button(children, on_press=on_smash) | ||
|
||
|
||
@ui.component | ||
def toolbar(): | ||
return [ | ||
custom_button("Play Movie", on_smash=lambda: print("Playing!")), | ||
custom_button("Upload Image", on_smash=lambda: print("Uploading!")), | ||
] | ||
|
||
|
||
handler_name_example = toolbar() | ||
``` | ||
|
||
In this example, `ui.button(children, on_press=on_smash)` shows that the `ui.button` still needs a prop called `on_press`, but the prop name received by your `custom_button` component is up to you. | ||
|
||
When your component supports multiple interactions, you might name event handler props for app-specific concepts. For example, this `toolbar` component receives `on_play_movie` and `on_upload_image` event handlers: | ||
|
||
```python | ||
from deephaven import ui | ||
|
||
|
||
@ui.component | ||
def custom_button(*children, on_press): | ||
return ui.button(children, on_press=on_press) | ||
|
||
|
||
@ui.component | ||
def toolbar(on_play_movie, on_upload_image): | ||
return [ | ||
custom_button("Play Movie", on_press=on_play_movie), | ||
custom_button("Upload Image", on_press=on_upload_image), | ||
] | ||
|
||
|
||
@ui.component | ||
def app(): | ||
return toolbar( | ||
on_play_movie=lambda: print("Playing!"), | ||
on_upload_image=lambda: print("Uploading!"), | ||
) | ||
|
||
|
||
app_example = app() | ||
``` | ||
|
||
Notice how the `app` component does not need to know what `toolbar` will do with `on_play_movie` or `on_upload_image`. That is an implementation detail of the `toolbar`. Here, `toolbar` passes them down as `on_press` handlers to its buttons, but it could later also trigger them on a keyboard shortcut. Naming props after app-specific interactions like `on_play_movie` gives you the flexibility to change how they’re used later. | ||
|
||
## Can event handlers have side effects? | ||
|
||
Yes. Event handlers are the best place for side effects. | ||
|
||
Unlike rendering functions, event handlers do not need to be [pure](../describing/pure_components.md), so it’s a great place to change something. For example, you can change an input’s value in response to typing, or change a list in response to a button press. |
Oops, something went wrong.