|
116 | 116 |
|
117 | 117 | .. autofunction:: pin_wait_change |
118 | 118 | .. autofunction:: pin_update |
| 119 | +.. autofunction:: pin_on_change |
119 | 120 |
|
120 | 121 | """ |
121 | 122 |
|
122 | 123 | import string |
| 124 | +import threading |
| 125 | +from collections import defaultdict |
123 | 126 |
|
124 | 127 | from pywebio.input import parse_input_update_spec |
125 | 128 | from pywebio.output import OutputPosition, Output |
126 | 129 | from pywebio.output import _get_output_spec |
127 | 130 | from .io_ctrl import send_msg, single_input_kwargs |
128 | | -from .session import next_client_event, chose_impl |
| 131 | +from .session import next_client_event, chose_impl, get_current_session, get_session_implement, CoroutineBasedSession, \ |
| 132 | + run_async, register_thread, SessionException |
| 133 | +from .utils import run_as_function, to_coroutine |
129 | 134 |
|
130 | 135 | _html_value_chars = set(string.ascii_letters + string.digits + '_') |
131 | 136 |
|
132 | 137 | __all__ = ['put_input', 'put_textarea', 'put_select', 'put_checkbox', 'put_radio', 'put_slider', 'put_actions', |
133 | | - 'pin', 'pin_update', 'pin_wait_change'] |
| 138 | + 'pin', 'pin_update', 'pin_wait_change', 'pin_on_change'] |
134 | 139 |
|
135 | 140 |
|
136 | 141 | def check_name(name): |
@@ -328,3 +333,53 @@ def pin_update(name, **spec): |
328 | 333 | check_name(name) |
329 | 334 | attributes = parse_input_update_spec(spec) |
330 | 335 | send_msg('pin_update', spec=dict(name=name, attributes=attributes)) |
| 336 | + |
| 337 | + |
| 338 | +def pin_on_change(name, onchange, clear=False): |
| 339 | + """ |
| 340 | + Bind a callback function to pin widget, the function will be called when user change the value of the pin widget. |
| 341 | +
|
| 342 | + The ``onchange`` callback is invoked with one argument, the changed value of the pin widget. |
| 343 | + You can bind multiple functions to one pin widget, those functions will be invoked sequentially |
| 344 | + (default behavior, can be changed by `clear` parameter). |
| 345 | +
|
| 346 | + :param str name: pin widget name |
| 347 | + :param callable onchange: callback function |
| 348 | + :param bool clear: whether to clear the previous callbacks bound to this pin widget |
| 349 | +
|
| 350 | + .. versionadded:: 1.6 |
| 351 | + """ |
| 352 | + current_session = get_current_session() |
| 353 | + |
| 354 | + def pin_on_change_gen(): |
| 355 | + while True: |
| 356 | + names = list(current_session.internal_save['pin_on_change_callbacks'].keys()) |
| 357 | + if not names: |
| 358 | + continue |
| 359 | + |
| 360 | + info = yield pin_wait_change(*names) |
| 361 | + callbacks = current_session.internal_save['pin_on_change_callbacks'][info['name']] |
| 362 | + for callback in callbacks: |
| 363 | + try: |
| 364 | + callback(info['value']) |
| 365 | + except Exception as e: |
| 366 | + if not isinstance(e, SessionException): |
| 367 | + current_session.on_task_exception() |
| 368 | + |
| 369 | + first_run = False |
| 370 | + if 'pin_on_change_callbacks' not in current_session.internal_save: |
| 371 | + current_session.internal_save['pin_on_change_callbacks'] = defaultdict(list) |
| 372 | + first_run = True |
| 373 | + |
| 374 | + if clear: |
| 375 | + current_session.internal_save['pin_on_change_callbacks'][name] = [onchange] |
| 376 | + else: |
| 377 | + current_session.internal_save['pin_on_change_callbacks'][name].append(onchange) |
| 378 | + |
| 379 | + if first_run: |
| 380 | + if get_session_implement() == CoroutineBasedSession: |
| 381 | + run_async(to_coroutine(pin_on_change_gen())) |
| 382 | + else: |
| 383 | + t = threading.Thread(target=lambda: run_as_function(pin_on_change_gen()), daemon=True) |
| 384 | + register_thread(t) |
| 385 | + t.start() |
0 commit comments