浏览代码

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,
 * 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
 popup
 ^^^^^^^^^^^^^^^
 ^^^^^^^^^^^^^^^
 Show 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,
  * 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.
    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.
  * 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.
  * Pin functions have additional ``scope`` and ``position`` parameters for output control.
 
 
 .. autofunction:: put_input
 .. autofunction:: put_input
@@ -121,16 +122,12 @@ Pin utils
 """
 """
 
 
 import string
 import string
-import threading
-from collections import defaultdict
 
 
 from pywebio.input import parse_input_update_spec
 from pywebio.input import parse_input_update_spec
 from pywebio.output import OutputPosition, Output
 from pywebio.output import OutputPosition, Output
 from pywebio.output import _get_output_spec
 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 + '_-')
 _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))
     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.
     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 str name: pin widget name
     :param callable onchange: callback function
     :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
     .. 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:
     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 {Command, Session} from "../session";
 import {CommandHandler} from "./base";
 import {CommandHandler} from "./base";
-import {GetPinValue, PinUpdate, WaitChange} from "../models/pin";
+import {GetPinValue, PinChangeCallback, PinUpdate, WaitChange} from "../models/pin";
 import {state} from "../state";
 import {state} from "../state";
 
 
 
 
 export class PinHandler implements CommandHandler {
 export class PinHandler implements CommandHandler {
     session: Session;
     session: Session;
 
 
-    accept_command = ['pin_value', 'pin_update', 'pin_wait'];
+    accept_command = ['pin_value', 'pin_update', 'pin_wait', 'pin_onchange'];
 
 
     constructor(session: Session) {
     constructor(session: Session) {
         this.session = session;
         this.session = session;
@@ -28,6 +28,8 @@ export class PinHandler implements CommandHandler {
                 console.error('error in `pin_wait`: %s', error);
                 console.error('error in `pin_wait`: %s', error);
                 state.CurrentSession.send_message({event: "js_yield", task_id: msg.task_id, data: null});
                 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 {t} from "../i18n";
 import {AfterCurrentOutputWidgetShow} from "../handlers/output";
 import {AfterCurrentOutputWidgetShow} from "../handlers/output";
 import {randomid} from "../utils";
 import {randomid} from "../utils";
+import {pushData} from "../session";
 
 
 
 
 let name2input: { [k: string]: InputItem } = {};
 let name2input: { [k: string]: InputItem } = {};
@@ -17,7 +18,8 @@ export function PinUpdate(name: string, attributes: { [k: string]: any }) {
     name2input[name].update_input({attributes: attributes});
     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) {
 export function WaitChange(names: string[], timeout: number) {
     let promises = [];
     let promises = [];
@@ -39,26 +41,40 @@ export function WaitChange(names: string[], timeout: number) {
     return Promise.race(promises).then((val) => {
     return Promise.race(promises).then((val) => {
         for (let name of names) {
         for (let name of names) {
             let callback_id = callback_ids[name];
             let callback_id = callback_ids[name];
-            delete onchange_callbacks[name][callback_id];
+            delete disposable_onchange_callbacks[name][callback_id];
         }
         }
         return val;
         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 {
 function register_on_change(name: string, callback: (val: any) => void): string {
     let callback_id = randomid(10);
     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;
     return callback_id;
 }
 }
 
 
 function trigger_onchange_event(name: string, value: any) {
 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 => {
     Object.keys(resolve_list).forEach(callback_id => {
         let resolve = resolve_list[callback_id];
         let resolve = resolve_list[callback_id];
         resolve(value);
         resolve(value);
     })
     })
+    for (let resolve of (resident_onchange_callbacks[name] || [])) {
+        resolve(value);
+    }
 }
 }
 
 
 export let PinWidget = {
 export let PinWidget = {