|
| 1 | +# Multi-Widget Pages |
| 2 | + |
| 3 | +`show_plotly()` and `show_dataframe()` each render a single widget. To combine multiple widgets — charts, grids, forms, tickers — in one window, use `build_html()` to generate each piece and compose them with `Div`. |
| 4 | + |
| 5 | +## The Pattern |
| 6 | + |
| 7 | +1. **Generate HTML snippets** — `build_html()` on any component returns a self-contained HTML string. |
| 8 | +2. **Compose with `Div`** — Nest `Div` objects to build your page tree. Style with CSS using `--pywry-*` theme variables. |
| 9 | +3. **Show** — Pass combined HTML as `HtmlContent` to `app.show()` with `include_plotly` / `include_aggrid` flags as needed. |
| 10 | + |
| 11 | +Every toolbar component (`Button`, `Select`, `Toggle`, `TextInput`, `Checkbox`, `RadioGroup`, `TabGroup`, `SliderInput`, `RangeInput`, `NumberInput`, `DateInput`, `SearchInput`, `SecretInput`, `TextArea`, `MultiSelect`, `Marquee`, `TickerItem`), plus `Toolbar`, `Modal`, and `Div` all expose `build_html()`. See the [Toolbar System](toolbars.md) and [Modals](modals.md) guides for their APIs. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## Widget Snippets |
| 16 | + |
| 17 | +### Plotly |
| 18 | + |
| 19 | +```python |
| 20 | +import json |
| 21 | +from pywry.templates import build_plotly_init_script |
| 22 | + |
| 23 | +fig_dict = json.loads(fig.to_json()) # must be a plain dict, not a Figure |
| 24 | +chart_html = build_plotly_init_script(figure=fig_dict, chart_id="my-chart") |
| 25 | +``` |
| 26 | + |
| 27 | +Requires `include_plotly=True` on `app.show()`. Target later with `widget.emit("plotly:update-figure", {"figure": new_dict, "chartId": "my-chart"})`. |
| 28 | + |
| 29 | +### AG Grid |
| 30 | + |
| 31 | +```python |
| 32 | +from pywry.grid import build_grid_config, build_grid_html |
| 33 | + |
| 34 | +config = build_grid_config(df, grid_id="my-grid", row_selection=True) |
| 35 | +grid_html = build_grid_html(config) |
| 36 | +``` |
| 37 | + |
| 38 | +Requires `include_aggrid=True` on `app.show()`. Target later with `widget.emit("grid:update-data", {"data": rows, "gridId": "my-grid"})`. |
| 39 | + |
| 40 | +Use unique `chart_id` / `grid_id` values when placing multiple charts or grids on the same page. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Composing with `Div` |
| 45 | + |
| 46 | +`Div` is the layout primitive. Use `content` for raw HTML (widget snippets, headings, text) and `children` for nested component objects. Both render in order: `content` first, then `children`. |
| 47 | + |
| 48 | +```python |
| 49 | +from pywry import Div, Button, Toggle |
| 50 | + |
| 51 | +dashboard = Div( |
| 52 | + class_name="dashboard", |
| 53 | + children=[ |
| 54 | + Div(class_name="kpi-row", children=[ |
| 55 | + Div(class_name="kpi-card", content='<span class="label">Revenue</span><span class="value">$318K</span>'), |
| 56 | + Div(class_name="kpi-card", content='<span class="label">Users</span><span class="value">1,247</span>'), |
| 57 | + ]), |
| 58 | + Div(class_name="content-row", children=[ |
| 59 | + Div(class_name="chart-panel", content=chart_html), |
| 60 | + Div(class_name="grid-panel", content=grid_html), |
| 61 | + ]), |
| 62 | + Div(class_name="controls", children=[ |
| 63 | + Toggle(label="Live:", event="app:live", value=True), |
| 64 | + Button(label="Export", event="app:export", variant="secondary"), |
| 65 | + ]), |
| 66 | + ], |
| 67 | +) |
| 68 | + |
| 69 | +page_html = dashboard.build_html() |
| 70 | +``` |
| 71 | + |
| 72 | +- `class_name` is added alongside the automatic `pywry-div` class — target it in CSS |
| 73 | +- Nested `Div`s pass parent context via `data-parent-id` automatically |
| 74 | + |
| 75 | +### Scripts |
| 76 | + |
| 77 | +`Div` and `Toolbar` accept a `script` field (inline JS or file path). `build_html()` resolves it and emits a `<script>` tag inside the `<div>`: |
| 78 | + |
| 79 | +```python |
| 80 | +panel = Div(content="<p>Hello</p>", script="console.log('loaded');") |
| 81 | +# → <div class="pywry-div" ...><p>Hello</p><script>console.log('loaded');</script></div> |
| 82 | + |
| 83 | +panel = Div(content="<p>Chart</p>", script="static/chart_init.js") # reads file |
| 84 | +``` |
| 85 | + |
| 86 | +`collect_scripts()` is also available if you need raw script strings without HTML wrapping. |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +## Showing the Page |
| 91 | + |
| 92 | +```python |
| 93 | +from pywry import PyWry, HtmlContent |
| 94 | + |
| 95 | +app = PyWry(title="Dashboard", width=1200, height=780) |
| 96 | + |
| 97 | +content = HtmlContent(html=dashboard.build_html(), inline_css=css) |
| 98 | + |
| 99 | +widget = app.show( |
| 100 | + content, |
| 101 | + include_plotly=True, |
| 102 | + include_aggrid=True, |
| 103 | + toolbars=[toolbar], # optional positioned toolbar bars |
| 104 | + modals=[modal], # optional modals |
| 105 | + callbacks={ |
| 106 | + "plotly:click": on_chart_click, |
| 107 | + "grid:row-selected": on_row_selected, |
| 108 | + "app:export": on_export, |
| 109 | + }, |
| 110 | +) |
| 111 | +``` |
| 112 | + |
| 113 | +- Components embedded directly in your HTML emit events via `window.pywry.emit()` without needing `toolbars=` |
| 114 | +- Toolbars passed via `toolbars=` are auto-positioned (top, bottom, left, right) |
| 115 | +- Modals passed via `modals=` are auto-injected with open/close wiring |
| 116 | + |
| 117 | +### Sizing Tips |
| 118 | + |
| 119 | +- Plotly: set `width: 100% !important; height: 100% !important` on `.pywry-plotly` and give its parent a flex layout |
| 120 | +- AG Grid: needs a parent with defined height — `flex: 1` inside a flex column works |
| 121 | +- Use `min-height: 0` on flex children that need to shrink below content size |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## Cross-Widget Events |
| 126 | + |
| 127 | +With everything in one page, wire interactions through callbacks — no JavaScript needed: |
| 128 | + |
| 129 | +```python |
| 130 | +# Grid selection → update chart |
| 131 | +def on_row_selected(data, _event_type, _label): |
| 132 | + rows = data.get("rows", []) |
| 133 | + widget.emit("plotly:update-figure", {"figure": filtered_fig, "chartId": "my-chart"}) |
| 134 | + |
| 135 | +# Chart click → update detail panel |
| 136 | +def on_chart_click(data, _event_type, _label): |
| 137 | + point = data.get("points", [{}])[0] |
| 138 | + widget.emit("pywry:set-content", {"id": "detail-panel", "html": f"<b>{point['x']}</b>"}) |
| 139 | + |
| 140 | +# Export CSV |
| 141 | +def on_export(_data, _event_type, _label): |
| 142 | + widget.emit("pywry:download", {"content": df.to_csv(index=False), "filename": "data.csv", "mimeType": "text/csv"}) |
| 143 | +``` |
| 144 | + |
| 145 | +See the [Event System guide](events.md) for the full list of system events (`pywry:set-content`, `pywry:download`, `plotly:update-figure`, `grid:update-data`, etc.). |
| 146 | + |
| 147 | +--- |
| 148 | + |
| 149 | +## Complete Example |
| 150 | + |
| 151 | +See [`examples/pywry_demo_multi_widget.py`](https://github.com/deeleeramone/PyWry/blob/main/pywry/examples/pywry_demo_multi_widget.py) for a full working dashboard with KPI cards, Plotly chart, AG Grid, toolbar, cross-widget filtering, and CSV export. |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Related Guides |
| 156 | + |
| 157 | +- [Toolbar System](toolbars.md) — all toolbar component types and their APIs |
| 158 | +- [Modals](modals.md) — modal overlay components |
| 159 | +- [Event System](events.md) — event registration and dispatch |
| 160 | +- [Theming & CSS](theming.md) — `--pywry-*` variables and theme switching |
| 161 | +- [HtmlContent](html-content.md) — CSS files, script files, inline CSS, JSON data |
| 162 | +- [Content Assembly](content-assembly.md) — what PyWry injects into the document |
0 commit comments