Skip to content

Commit ce2a047

Browse files
committed
refine pin_on_change()
1 parent c704557 commit ce2a047

File tree

4 files changed

+49
-49
lines changed

4 files changed

+49
-49
lines changed

docs/spec.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,16 @@ The ``spec`` fields of ``pin_wait`` commands:
295295
* timeout: int,
296296

297297

298+
pin_onchange
299+
^^^^^^^^^^^^^^^
300+
set a callback which is invoked when the value of pin widget is changed
301+
302+
The ``spec`` fields of ``pin_onchange`` commands:
303+
304+
* name: string
305+
* callback_id: string, if ``None``, not set callback
306+
* clear: bool
307+
298308
popup
299309
^^^^^^^^^^^^^^^
300310
Show popup

pywebio/pin.py

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
* The first parameter of pin widget function is always the name of the widget,
7979
and if you output two pin widgets with the same name, the previous one will expire.
8080
* Pin functions don't support the ``on_change`` and ``validate`` callbacks, and the ``required`` parameter.
81+
(There is a :func:`pin_on_change()` function as an alternative to ``on_change``)
8182
* Pin functions have additional ``scope`` and ``position`` parameters for output control.
8283
8384
.. autofunction:: put_input
@@ -121,16 +122,12 @@
121122
"""
122123

123124
import string
124-
import threading
125-
from collections import defaultdict
126125

127126
from pywebio.input import parse_input_update_spec
128127
from pywebio.output import OutputPosition, Output
129128
from pywebio.output import _get_output_spec
130-
from .io_ctrl import send_msg, single_input_kwargs
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+
from .io_ctrl import send_msg, single_input_kwargs, output_register_callback
130+
from .session import next_client_event, chose_impl
134131

135132
_pin_name_chars = set(string.ascii_letters + string.digits + '_-')
136133

@@ -334,7 +331,7 @@ def pin_update(name, **spec):
334331
send_msg('pin_update', spec=dict(name=name, attributes=attributes))
335332

336333

337-
def pin_on_change(name, onchange, clear=False):
334+
def pin_on_change(name, onchange=None, clear=False, **callback_options):
338335
"""
339336
Bind a callback function to pin widget, the function will be called when user change the value of the pin widget.
340337
@@ -344,41 +341,16 @@ def pin_on_change(name, onchange, clear=False):
344341
345342
:param str name: pin widget name
346343
:param callable onchange: callback function
347-
:param bool clear: whether to clear the previous callbacks bound to this pin widget
344+
:param bool clear: whether to clear the previous callbacks bound to this pin widget.
345+
If you just want to clear callbacks and not set new callback, use ``pin_on_change(name, clear=True)``.
346+
:param callback_options: Other options of the ``onclick`` callback.
347+
Refer to the ``callback_options`` parameter of :func:`put_buttons() <pywebio.output.put_buttons>`
348348
349349
.. versionadded:: 1.6
350350
"""
351-
current_session = get_current_session()
352-
353-
def pin_on_change_gen():
354-
while True:
355-
names = list(current_session.internal_save['pin_on_change_callbacks'].keys())
356-
if not names:
357-
continue
358-
359-
info = yield pin_wait_change(*names)
360-
callbacks = current_session.internal_save['pin_on_change_callbacks'][info['name']]
361-
for callback in callbacks:
362-
try:
363-
callback(info['value'])
364-
except Exception as e:
365-
if not isinstance(e, SessionException):
366-
current_session.on_task_exception()
367-
368-
first_run = False
369-
if 'pin_on_change_callbacks' not in current_session.internal_save:
370-
current_session.internal_save['pin_on_change_callbacks'] = defaultdict(list)
371-
first_run = True
372-
373-
if clear:
374-
current_session.internal_save['pin_on_change_callbacks'][name] = [onchange]
351+
assert not (onchange is None and clear is False), "When `onchange` is `None`, `clear` must be `True`"
352+
if onchange is not None:
353+
callback_id = output_register_callback(onchange, **callback_options)
375354
else:
376-
current_session.internal_save['pin_on_change_callbacks'][name].append(onchange)
377-
378-
if first_run:
379-
if get_session_implement() == CoroutineBasedSession:
380-
run_async(to_coroutine(pin_on_change_gen()))
381-
else:
382-
t = threading.Thread(target=lambda: run_as_function(pin_on_change_gen()), daemon=True)
383-
register_thread(t)
384-
t.start()
355+
callback_id = None
356+
send_msg('pin_onchange', spec=dict(name=name, callback_id=callback_id, clear=clear))

webiojs/src/handlers/pin.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {Command, Session} from "../session";
22
import {CommandHandler} from "./base";
3-
import {GetPinValue, PinUpdate, WaitChange} from "../models/pin";
3+
import {GetPinValue, PinChangeCallback, PinUpdate, WaitChange} from "../models/pin";
44
import {state} from "../state";
55

66

77
export class PinHandler implements CommandHandler {
88
session: Session;
99

10-
accept_command = ['pin_value', 'pin_update', 'pin_wait'];
10+
accept_command = ['pin_value', 'pin_update', 'pin_wait', 'pin_onchange'];
1111

1212
constructor(session: Session) {
1313
this.session = session;
@@ -28,6 +28,8 @@ export class PinHandler implements CommandHandler {
2828
console.error('error in `pin_wait`: %s', error);
2929
state.CurrentSession.send_message({event: "js_yield", task_id: msg.task_id, data: null});
3030
});
31+
}else if (msg.command === 'pin_onchange') {
32+
PinChangeCallback(msg.spec.name, msg.spec.callback_id, msg.spec.clear);
3133
}
3234

3335
}

webiojs/src/models/pin.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {InputItem} from "./input/base";
33
import {t} from "../i18n";
44
import {AfterCurrentOutputWidgetShow} from "../handlers/output";
55
import {randomid} from "../utils";
6+
import {pushData} from "../session";
67

78

89
let name2input: { [k: string]: InputItem } = {};
@@ -17,7 +18,8 @@ export function PinUpdate(name: string, attributes: { [k: string]: any }) {
1718
name2input[name].update_input({attributes: attributes});
1819
}
1920

20-
let onchange_callbacks: { [name: string]: { [callback_id: string]: ((val: any) => void) } } = {}; // name->{callback_id->callback}
21+
let disposable_onchange_callbacks: { [name: string]: { [callback_id: string]: ((val: any) => void) } } = {}; // name->{callback_id->callback}
22+
let resident_onchange_callbacks: { [name: string]: ((val: any) => void)[] } = {}; // name->[]
2123

2224
export function WaitChange(names: string[], timeout: number) {
2325
let promises = [];
@@ -39,26 +41,40 @@ export function WaitChange(names: string[], timeout: number) {
3941
return Promise.race(promises).then((val) => {
4042
for (let name of names) {
4143
let callback_id = callback_ids[name];
42-
delete onchange_callbacks[name][callback_id];
44+
delete disposable_onchange_callbacks[name][callback_id];
4345
}
4446
return val;
4547
});
4648
}
4749

50+
export function PinChangeCallback(name: string, callback_id: string, clear: boolean) {
51+
if (!(name in resident_onchange_callbacks) || clear)
52+
resident_onchange_callbacks[name] = [];
53+
54+
if (callback_id) {
55+
resident_onchange_callbacks[name].push((val) => {
56+
pushData(val, callback_id)
57+
})
58+
}
59+
}
60+
4861
function register_on_change(name: string, callback: (val: any) => void): string {
4962
let callback_id = randomid(10);
50-
if (!(name in onchange_callbacks))
51-
onchange_callbacks[name] = {};
52-
onchange_callbacks[name][callback_id] = callback;
63+
if (!(name in disposable_onchange_callbacks))
64+
disposable_onchange_callbacks[name] = {};
65+
disposable_onchange_callbacks[name][callback_id] = callback;
5366
return callback_id;
5467
}
5568

5669
function trigger_onchange_event(name: string, value: any) {
57-
let resolve_list = onchange_callbacks[name] || {};
70+
let resolve_list = disposable_onchange_callbacks[name] || {};
5871
Object.keys(resolve_list).forEach(callback_id => {
5972
let resolve = resolve_list[callback_id];
6073
resolve(value);
6174
})
75+
for (let resolve of (resident_onchange_callbacks[name] || [])) {
76+
resolve(value);
77+
}
6278
}
6379

6480
export let PinWidget = {

0 commit comments

Comments
 (0)