Files
chrani-bot-tng/bot/resources/GUIDELINES_MODULE_WIDGET_MENU.md
2025-11-21 07:26:02 +01:00

7.8 KiB
Raw Permalink Blame History

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):

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_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.