Files
chrani-bot-tng/bot/resources/GUIDELINES_MODULE_WIDGET_MENU.md

212 lines
7.8 KiB
Markdown
Raw Permalink Normal View History

2025-11-21 07:26:02 +01:00
### Add a menu button and view to a module widget (HowTo)
This guide explains how to add a new button to a widget's pullout menu and display a corresponding screen ("view"). It follows the menu pattern used in the `locations` module and the example implemented in the `telnet` module.
The pattern consists of four parts:
- A view registry in the widget Python file that defines available views and their labels.
- A shared Jinja2 macro `construct_view_menu` to render the menu.
- A small widget template `control_view_menu.html` that invokes the macro for the module.
- A server action `toggle_<module>_widget_view` that switches the current view.
Below, `<module>` stands for your module name (e.g., `telnet`, `locations`), and `<view_id>` is the identifier of the view you are adding (e.g., `test`).
---
#### 1) Ensure the view menu macro is available for your module
Your module's Jinja2 macros file (e.g., `bot/modules/<module>/templates/jinja2_macros.html`) should contain the macro `construct_view_menu`. If your module doesn't have it yet, mirror the implementation from:
- `bot/modules/locations/templates/jinja2_macros.html`
The macro expects these parameters:
- `views`: dict of view entries (see step 2)
- `current_view`: name of the active view
- `module_name`: the module identifier (e.g., `telnet`)
- `steamid`: the current user's steamid
- `default_view`: the view to return to from an active state (usually `frontend`)
The macro renders toggle links that emit the standard socket event:
```
['widget_event', [module_name, ['toggle_' ~ module_name ~ '_widget_view', {'steamid': steamid, 'action': action}]]]
```
---
#### 2) Define or extend the VIEW_REGISTRY in the widget Python file
In your widget file (e.g., `bot/modules/<module>/widgets/<widget_name>.py`) define a `VIEW_REGISTRY` mapping of view IDs to config objects. Each entry can have:
- `label_active`: label when this view is currently active (typically `back`)
- `label_inactive`: label when the view is not active (e.g., `options`, `test`)
- `action`: the action string to be sent by the menu (e.g., `show_options`, `show_test`)
- `include_in_menu`: whether to show it in the menu (set `False` for base `frontend`)
Example (excerpt with a new `test` view):
```python
VIEW_REGISTRY = {
'frontend': {
'label_active': 'back',
'label_inactive': 'main',
'action': 'show_frontend',
'include_in_menu': False
},
'options': {
'label_active': 'back',
'label_inactive': 'options',
'action': 'show_options',
'include_in_menu': True
},
'test': {
'label_active': 'back',
'label_inactive': 'test',
'action': 'show_test',
'include_in_menu': True
}
}
```
Update `select_view` to route to your new view:
```python
def select_view(*args, **kwargs):
module = args[0]
dispatchers_steamid = kwargs.get('dispatchers_steamid', None)
current_view = module.get_current_view(dispatchers_steamid)
if current_view == 'options':
options_view(module, dispatchers_steamid=dispatchers_steamid)
elif current_view == 'test':
test_view(module, dispatchers_steamid=dispatchers_steamid)
else:
frontend_view(module, dispatchers_steamid=dispatchers_steamid)
```
---
#### 3) Create the control_view_menu.html template for the widget
Create a template next to your widget's templates folder that renders the menu by importing the macro and passing the registry:
`bot/modules/<module>/templates/<widget_name>/control_view_menu.html`
```jinja2
{%- from 'jinja2_macros.html' import construct_view_menu with context -%}
<div id="<widget_dom_id>_options_toggle" class="pull_out right">
{{ construct_view_menu(
views=views,
current_view=current_view,
module_name='<module>',
steamid=steamid,
default_view='frontend'
)}}
</div>
```
Replace `<widget_dom_id>` with the widget DOM id (e.g., `telnet_table_widget_options_toggle`), and `<module>` with the module name literal (e.g., `telnet`).
---
#### 4) Render the menu in each view and add your new view function
In every view function (`frontend_view`, `options_view`, and your new `test_view`) render the menu and include it into the page as `options_toggle`:
```python
template_view_menu = module.templates.get_template('<widget_name>/control_view_menu.html')
current_view = module.get_current_view(dispatchers_steamid)
options_toggle = module.template_render_hook(
module,
template=template_view_menu,
views=VIEW_REGISTRY,
current_view=current_view,
steamid=dispatchers_steamid
)
data_to_emit = module.template_render_hook(
module,
template=template_for_this_view,
options_toggle=options_toggle,
# ... other template params ...
)
```
Create the new view template (e.g., `view_test.html`) that uses the aside area to render the menu:
```jinja2
<header>
<div>
<span>Some Title</span>
</div>
</header>
<aside>
{{ options_toggle }}
</aside>
<main>
<table>
<thead>
<tr>
<th>Test View</th>
</tr>
</thead>
<tbody>
<tr><td><span>Test</span></td></tr>
</tbody>
</table>
</main>
```
---
#### 5) Wire the server action to toggle views
Each module has an action named `toggle_<module>_widget_view` that updates the current view for a dispatcher. Extend it to support your new action string (`show_<view_id>`):
`bot/modules/<module>/actions/toggle_<module>_widget_view.py`
```python
def main_function(module, event_data, dispatchers_steamid):
action = event_data[1].get('action', None)
event_data[1]['action_identifier'] = action_name
if action == 'show_options':
current_view = 'options'
elif action == 'show_frontend':
current_view = 'frontend'
elif action == 'show_test':
current_view = 'test'
else:
module.callback_fail(callback_fail, module, event_data, dispatchers_steamid)
return
module.set_current_view(dispatchers_steamid, {
'current_view': current_view
})
module.callback_success(callback_success, module, event_data, dispatchers_steamid)
```
Make sure the view ID you set here matches the view IDs used in `VIEW_REGISTRY` and `select_view`.
---
#### 6) Reference implementation to compare
Use these files as working examples:
- Locations menu macro: `bot/modules/locations/templates/jinja2_macros.html`
- Locations widget menu template: `bot/modules/locations/templates/manage_locations_widget/control_view_menu.html`
- Telnet macros with menu: `bot/modules/telnet/templates/jinja2_macros.html`
- Telnet menu template: `bot/modules/telnet/templates/telnet_log_widget/control_view_menu.html`
- Telnet widget with added view: `bot/modules/telnet/widgets/telnet_log_widget.py`
- Telnet action wiring: `bot/modules/telnet/actions/toggle_telnet_widget_view.py`
- Telnet new view template: `bot/modules/telnet/templates/telnet_log_widget/view_test.html`
---
#### 7) Quick checklist
- [ ] `VIEW_REGISTRY` contains your `<view_id>` with a correct `action` (`show_<view_id>`) and `include_in_menu = True`.
- [ ] `select_view` routes to `<view_id>` by calling `<view_id>_view`.
- [ ] The new `<view_id>_view` renders `options_toggle` using `control_view_menu.html`.
- [ ] The new view has a template and includes `{{ options_toggle }}` in the aside.
- [ ] The module's `toggle_<module>_widget_view` action handles `show_<view_id>` and sets `current_view` accordingly.
- [ ] Menu renders without errors and buttons switch between views (`back` returns to `frontend`).
---
#### Notes
- Keep naming consistent: action strings are `show_<view_id>`, event name is `toggle_<module>_widget_view`.
- The base view `frontend` is usually not included in the menu (`include_in_menu: False`), but is the `default_view` used for the "back" behavior.
- Follow the module's existing code style and avoid unrelated refactors when introducing new views/buttons.