瀏覽代碼

refine `pin_on_change()`

wangweimin 3 年之前
父節點
當前提交
ce2a047afa
共有 4 個文件被更改,包括 49 次插入49 次删除
  1. 10 0
      docs/spec.rst
  2. 13 41
      pywebio/pin.py
  3. 4 2
      webiojs/src/handlers/pin.ts
  4. 22 6
      webiojs/src/models/pin.ts

+ 10 - 0
docs/spec.rst

@@ -295,6 +295,16 @@ The ``spec`` fields of ``pin_wait`` commands:
 * timeout: int,
 
 
+pin_onchange
+^^^^^^^^^^^^^^^
+set a callback which is invoked when the value of pin widget is changed
+
+The ``spec`` fields of ``pin_onchange`` commands:
+
+* name: string
+* callback_id: string, if ``None``, not set callback
+* clear: bool
+
 popup
 ^^^^^^^^^^^^^^^
 Show popup

+ 13 - 41
pywebio/pin.py

@@ -78,6 +78,7 @@ Here lists the difference between the two in parameters:
  * The first parameter of pin widget function is always the name of the widget,
    and if you output two pin widgets with the same name, the previous one will expire.
  * Pin functions don't support the ``on_change`` and ``validate`` callbacks, and the ``required`` parameter.
+   (There is a :func:`pin_on_change()` function as an alternative to ``on_change``)
  * Pin functions have additional ``scope`` and ``position`` parameters for output control.
 
 .. autofunction:: put_input
@@ -121,16 +122,12 @@ Pin utils
 """
 
 import string
-import threading
-from collections import defaultdict
 
 from pywebio.input import parse_input_update_spec
 from pywebio.output import OutputPosition, Output
 from pywebio.output import _get_output_spec
-from .io_ctrl import send_msg, single_input_kwargs
-from .session import next_client_event, chose_impl, get_current_session, get_session_implement, CoroutineBasedSession, \
-    run_async, register_thread, SessionException
-from .utils import run_as_function, to_coroutine
+from .io_ctrl import send_msg, single_input_kwargs, output_register_callback
+from .session import next_client_event, chose_impl
 
 _pin_name_chars = set(string.ascii_letters + string.digits + '_-')
 
@@ -334,7 +331,7 @@ def pin_update(name, **spec):
     send_msg('pin_update', spec=dict(name=name, attributes=attributes))
 
 
-def pin_on_change(name, onchange, clear=False):
+def pin_on_change(name, onchange=None, clear=False, **callback_options):
     """
     Bind a callback function to pin widget, the function will be called when user change the value of the pin widget.
 
@@ -344,41 +341,16 @@ def pin_on_change(name, onchange, clear=False):
 
     :param str name: pin widget name
     :param callable onchange: callback function
-    :param bool clear: whether to clear the previous callbacks bound to this pin widget
+    :param bool clear: whether to clear the previous callbacks bound to this pin widget.
+       If you just want to clear callbacks and not set new callback, use ``pin_on_change(name, clear=True)``.
+    :param callback_options: Other options of the ``onclick`` callback.
+       Refer to the ``callback_options`` parameter of :func:`put_buttons() <pywebio.output.put_buttons>`
 
     .. versionadded:: 1.6
     """
-    current_session = get_current_session()
-
-    def pin_on_change_gen():
-        while True:
-            names = list(current_session.internal_save['pin_on_change_callbacks'].keys())
-            if not names:
-                continue
-
-            info = yield pin_wait_change(*names)
-            callbacks = current_session.internal_save['pin_on_change_callbacks'][info['name']]
-            for callback in callbacks:
-                try:
-                    callback(info['value'])
-                except Exception as e:
-                    if not isinstance(e, SessionException):
-                        current_session.on_task_exception()
-
-    first_run = False
-    if 'pin_on_change_callbacks' not in current_session.internal_save:
-        current_session.internal_save['pin_on_change_callbacks'] = defaultdict(list)
-        first_run = True
-
-    if clear:
-        current_session.internal_save['pin_on_change_callbacks'][name] = [onchange]
+    assert not (onchange is None and clear is False), "When `onchange` is `None`, `clear` must be `True`"
+    if onchange is not None:
+        callback_id = output_register_callback(onchange, **callback_options)
     else:
-        current_session.internal_save['pin_on_change_callbacks'][name].append(onchange)
-
-    if first_run:
-        if get_session_implement() == CoroutineBasedSession:
-            run_async(to_coroutine(pin_on_change_gen()))
-        else:
-            t = threading.Thread(target=lambda: run_as_function(pin_on_change_gen()), daemon=True)
-            register_thread(t)
-            t.start()
+        callback_id = None
+    send_msg('pin_onchange', spec=dict(name=name, callback_id=callback_id, clear=clear))

+ 4 - 2
webiojs/src/handlers/pin.ts

@@ -1,13 +1,13 @@
 import {Command, Session} from "../session";
 import {CommandHandler} from "./base";
-import {GetPinValue, PinUpdate, WaitChange} from "../models/pin";
+import {GetPinValue, PinChangeCallback, PinUpdate, WaitChange} from "../models/pin";
 import {state} from "../state";
 
 
 export class PinHandler implements CommandHandler {
     session: Session;
 
-    accept_command = ['pin_value', 'pin_update', 'pin_wait'];
+    accept_command = ['pin_value', 'pin_update', 'pin_wait', 'pin_onchange'];
 
     constructor(session: Session) {
         this.session = session;
@@ -28,6 +28,8 @@ export class PinHandler implements CommandHandler {
                 console.error('error in `pin_wait`: %s', error);
                 state.CurrentSession.send_message({event: "js_yield", task_id: msg.task_id, data: null});
             });
+        }else if (msg.command === 'pin_onchange') {
+            PinChangeCallback(msg.spec.name, msg.spec.callback_id, msg.spec.clear);
         }
 
     }

+ 22 - 6
webiojs/src/models/pin.ts

@@ -3,6 +3,7 @@ import {InputItem} from "./input/base";
 import {t} from "../i18n";
 import {AfterCurrentOutputWidgetShow} from "../handlers/output";
 import {randomid} from "../utils";
+import {pushData} from "../session";
 
 
 let name2input: { [k: string]: InputItem } = {};
@@ -17,7 +18,8 @@ export function PinUpdate(name: string, attributes: { [k: string]: any }) {
     name2input[name].update_input({attributes: attributes});
 }
 
-let onchange_callbacks: { [name: string]: { [callback_id: string]: ((val: any) => void) } } = {}; // name->{callback_id->callback}
+let disposable_onchange_callbacks: { [name: string]: { [callback_id: string]: ((val: any) => void) } } = {}; // name->{callback_id->callback}
+let resident_onchange_callbacks: { [name: string]: ((val: any) => void)[] } = {}; // name->[]
 
 export function WaitChange(names: string[], timeout: number) {
     let promises = [];
@@ -39,26 +41,40 @@ export function WaitChange(names: string[], timeout: number) {
     return Promise.race(promises).then((val) => {
         for (let name of names) {
             let callback_id = callback_ids[name];
-            delete onchange_callbacks[name][callback_id];
+            delete disposable_onchange_callbacks[name][callback_id];
         }
         return val;
     });
 }
 
+export function PinChangeCallback(name: string, callback_id: string, clear: boolean) {
+    if (!(name in resident_onchange_callbacks) || clear)
+        resident_onchange_callbacks[name] = [];
+
+    if (callback_id) {
+        resident_onchange_callbacks[name].push((val) => {
+            pushData(val, callback_id)
+        })
+    }
+}
+
 function register_on_change(name: string, callback: (val: any) => void): string {
     let callback_id = randomid(10);
-    if (!(name in onchange_callbacks))
-        onchange_callbacks[name] = {};
-    onchange_callbacks[name][callback_id] = callback;
+    if (!(name in disposable_onchange_callbacks))
+        disposable_onchange_callbacks[name] = {};
+    disposable_onchange_callbacks[name][callback_id] = callback;
     return callback_id;
 }
 
 function trigger_onchange_event(name: string, value: any) {
-    let resolve_list = onchange_callbacks[name] || {};
+    let resolve_list = disposable_onchange_callbacks[name] || {};
     Object.keys(resolve_list).forEach(callback_id => {
         let resolve = resolve_list[callback_id];
         resolve(value);
     })
+    for (let resolve of (resident_onchange_callbacks[name] || [])) {
+        resolve(value);
+    }
 }
 
 export let PinWidget = {