7.8 KiB
Add a menu button and view to a module widget (How‑To)
This guide explains how to add a new button to a widget's pull‑out 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_menuto render the menu. - A small widget template
control_view_menu.htmlthat invokes the macro for the module. - A server action
toggle_<module>_widget_viewthat 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 viewmodule_name: the module identifier (e.g.,telnet)steamid: the current user's steamiddefault_view: the view to return to from an active state (usuallyfrontend)
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 (typicallyback)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 (setFalsefor basefrontend)
Example (excerpt with a new test view):
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:
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
{%- 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:
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:
<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
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_REGISTRYcontains your<view_id>with a correctaction(show_<view_id>) andinclude_in_menu = True.select_viewroutes to<view_id>by calling<view_id>_view.- The new
<view_id>_viewrendersoptions_toggleusingcontrol_view_menu.html. - The new view has a template and includes
{{ options_toggle }}in the aside. - The module's
toggle_<module>_widget_viewaction handlesshow_<view_id>and setscurrent_viewaccordingly. - Menu renders without errors and buttons switch between views (
backreturns tofrontend).
Notes
- Keep naming consistent: action strings are
show_<view_id>, event name istoggle_<module>_widget_view. - The base view
frontendis usually not included in the menu (include_in_menu: False), but is thedefault_viewused for the "back" behavior. - Follow the module's existing code style and avoid unrelated refactors when introducing new views/buttons.