Skip to content

Commit

Permalink
Separate UI Customization into layout, themes and dynamic UI
Browse files Browse the repository at this point in the history
  • Loading branch information
andrie committed Aug 5, 2024
1 parent 1615153 commit dc36d20
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 214 deletions.
12 changes: 9 additions & 3 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ website:
- text: "Reactivity"
file: docs/reactivity-intro.qmd
- text: "UI Customization"
file: docs/ui-customization.qmd
file: docs/ui-layout.qmd
- text: "Best practices"
file: docs/best-practices.qmd
# - text: "Test"
Expand Down Expand Up @@ -75,8 +75,14 @@ website:

- title: "UI Customization"
contents:
- href: docs/ui-customization.qmd
text: Slides
- section: "Slides"
contents:
- href: docs/ui-layout.qmd
text: UI Layout
- href: docs/ui-themes.qmd
text: UI Themes
- href: docs/ui-dynamic.qmd
text: Dynamic UI
- section: "Exercises"
contents:
- docs/exercises/express-301-sidebar/index.qmd
Expand Down
12 changes: 0 additions & 12 deletions docs/ui-customization.qmd

This file was deleted.

232 changes: 232 additions & 0 deletions docs/ui-dynamic-slides.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
title: "Dynamic UI"
title-slide-attributes:
data-background-image: images/shiny-for-python-seattle.jpg
data-background-position: bottom left
data-background-size: cover
format:
positconfslides-revealjs:
incremental: false
chalkboard: true
slide-number: c/t
code-copy: true
center-title-slide: false
code-link: true
highlight-style: a11y
width: "1600"
height: "900"
css: "styles.css"
filters:
- positconfslides
- reveal-auto-agenda
auto-agenda:
heading: Agenda
---

```{python}
# | echo: false
import os
import sys
exercises_path = "./exercises"
if exercises_path not in sys.path:
sys.path.append(exercises_path)
from helpers import include_shiny_folder
```

# Composable functions

## This is getting a bit complicated

- UIs can start to get complicated
- You can end up with deeply nested function calls
- Too many brackets
- Too many indents
- Luckily You can break UIs into variables and compose them

## UI functions are composable

```{.python}
import shiny.experimental as x
ui.navset_tab(
ui.nav(
"Tab1",
ui.card(ui.output_plot("Plot")),
ui.output_text("some_text"),
),
ui.nav("Tab2", ui.output_data_frame("data")),
ui.nav("Tab3", ui.output_image("image")),
)
```

## UI functions are composable

```{.python}
card1 = ui.card(ui.output_plot("Plot"))
tab1 = ui.nav(
"Tab1",
card1,
ui.output_text("some_text"),
)
tab2 = ui.nav("Tab2", ui.output_data_frame("data"))
tab3 = ui.nav("Tab3", ui.output_image("image"))
ui = ui.fluid_page(ui.navset_tab(tab1, tab2, tab3))
```

## Your turn

{{< yourturn 'express-304-layout-columns' >}}

<!-- Go to [exercises/10-layout](./exercises/10-layout.html) or run `apps/core/exercises/4-ui-customization/4.3-layout` locally. -->

## Your turn

{{< yourturn 'express-305-render-express' >}}


# Review

## Review: Value boxes

- Value boxes are another experimental layout container
- Just like `ui.card()`
- Called with `ui.value_box()`
- Useful for visually highlighting important numbers or text
- Usually contain `ui.output_text()` as a child element

## Your turn

{{< yourturn 'express-307-value-box' >}}


# Dynamic user interfaces

## Why create dynamic UIs?

- Guide the user along a happy path
- If you don't want them to click on something, don't show it to them
- Much better to prevent errors than to handle them

## Dynamic UI example

{{< yourview '302-dynamic-ui' >}}


## Shiny Dynamic UI

Function | Description
------------------- | ----------------
`@render.ui` | Generate UI elements on the server
`ui.conditional_panel` | Hide things on the browser
`ui.update_*` | Modify existing UI elements

## Dynamic UI

- UI elements can be generated like any other element
- Use `@render.ui` on the server
- Function returns any `ui` element
- Referred to like a regular ui element

## Dynamic UI

{{< yourview '302-dynamic-ui' >}}


## UI side

```{.python code-line-numbers="5-6"}
from shiny.express import render, ui, input
with ui.card():
ui.input_checkbox("show_checkbox", "Show Checkbox")
with ui.panel_conditional( "input.show_checkbox"):
ui.input_checkbox("show_slider", "Show Slider"),
@render.ui
def dynamic_slider():
if input.show_slider():
return ui.input_slider("n", "N", 0, 100, 20)
```

## Server side

```{.python code-line-numbers="7-10"}
from shiny.express import render, ui, input
with ui.card():
ui.input_checkbox("show_checkbox", "Show Checkbox")
with ui.panel_conditional( "input.show_checkbox"):
ui.input_checkbox("show_slider", "Show Slider"),
@render.ui
def dynamic_slider():
if input.show_slider():
return ui.input_slider("n", "N", 0, 100, 20)
```

## Summary of dynamic UI

- Dynamic UIs can be intimidating, but they follow a familiar pattern:
- Create a rendering function which returns a UI chunk
- Decorate the function with and `@render.ui`

- Very powerful, but
- Round trip to the server
- You can lose session state

# Conditional panel

## Conditional panel
- `ui.panel_conditional` hides UI elements based on conditions
- Less flexible than `ui.output_ui`
- Preserves input state
- Doesn't require trip to the server

## Conditional panel

{{< yourview '302-dynamic-ui' >}}


## Conditional panel

```{.python code-line-numbers="3-6"}
app_ui = ui.page_fluid(
ui.input_checkbox("show_checkbox", "Show Checkbox"),
ui.panel_conditional(
"input.show_checkbox",
ui.input_checkbox("show_slider", "Show Slider"),
),
ui.output_ui("dynamic_slider"),
)
def server(input, output, session):
@output
@render.ui
def dynamic_slider():
print(input.show_slider())
if input.show_slider():
return ui.input_slider("n", "N", 0, 100, 20)
app = App(app_ui, server)
```

## Things to note
- Uses JavaScript condition, not python
- JavaScript condition will be the same as R, Google and chatGPT can help
- You often want `===`

## Your turn

{{< yourturn 'express-308-layout-column' >}}



# Conclusion

## That's a lot of stuff!

- Shiny gives you a lot of power to customize your app
- Important to remember _that_ you can do these things, even if you forget _how_
- Learning to build intuitive UIs is a journey
- Ask for advice on Discord
12 changes: 12 additions & 0 deletions docs/ui-dynamic.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Dynamic UI
---

To view in full screen, click on the slides then press {{< kbd f >}}, or <a href = "ui-dynamic-slides.html" target="_blank">open in new tab.</a>


```{=html}
<iframe class="slide-deck" src="ui-dynamic-slides.html" height="420" width="747" style="border: 1px solid #123233;"></iframe>
```

{{< yourturnIframeContainer >}}
Loading

0 comments on commit dc36d20

Please sign in to comment.