소스 검색

feat: `input` add `action` parameter

wangweimin 4 년 전
부모
커밋
ce9df38991
4개의 변경된 파일69개의 추가작업 그리고 13개의 파일을 삭제
  1. 3 0
      docs/spec.rst
  2. 5 1
      pywebio/html/css/app.css
  3. 33 3
      pywebio/input.py
  4. 28 9
      webiojs/src/models/input/input.ts

+ 3 - 0
docs/spec.rst

@@ -112,6 +112,9 @@ input_group
 
 不同输入类型的特有属性:
 
+* text,number,password:
+  * action: 在输入框一侧显示一个按钮。格式为 ``{label: 按钮标签, callback_id: 按钮回调id}``
+
 * textarea:
 
   * code: Codemirror 参数, 见 :func:`pywebio.input.textarea` 的 ``code`` 参数

+ 5 - 1
pywebio/html/css/app.css

@@ -179,4 +179,8 @@ details[open]>summary {
 }
 .hide{
     display: none;
-}
+}
+.single-input-action-btn{
+    border-top-right-radius: 0.25rem!important;
+    border-bottom-right-radius: 0.25rem!important;
+}

+ 33 - 3
pywebio/input.py

@@ -67,7 +67,7 @@ def _parse_args(kwargs, excludes=()):
     return kwargs, valid_func
 
 
-def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, placeholder=None, required=None,
+def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, action=None, placeholder=None, required=None,
           readonly=None, datalist=None, help_text=None, **other_html_attrs):
     r"""文本输入
 
@@ -87,6 +87,22 @@ def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, placeh
 
     :param name: 输入框的名字. 与 `input_group` 配合使用,用于在输入组的结果中标识不同输入项.  **在单个输入中,不可以设置该参数!**
     :param str value: 输入框的初始值
+    :type action: tuple(label:str, callback:callable)
+    :param action: 在输入框右侧显示一个按钮,可通过点击按钮为输入框设置值。
+
+       ``label`` 为按钮的显示文本, ``callback`` 为按钮点击的回调函数。
+
+       回调函数需要接收一个 ``set_value`` 位置参数, ``set_value`` 是一个可调用对象,签名为 ``set_value(value)`` ,
+       调用 ``set_value`` 即可设置输入框的值。
+
+       使用示例::
+
+            import time
+            def set_now_ts(set_value):
+                set_value(int(time.time()))
+
+            input('Timestamp', type=NUMBER, action=('Now', set_now_ts))
+
     :param str placeholder: 输入框的提示内容。提示内容会在输入框未输入值时以浅色字体显示在输入框中
     :param bool required: 当前输入是否为必填项
     :param bool readonly: 输入框是否为只读
@@ -96,7 +112,7 @@ def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, placeh
     :return: 用户输入的值
     """
 
-    item_spec, valid_func = _parse_args(locals())
+    item_spec, valid_func = _parse_args(locals(), excludes=('action',))
 
     # 参数检查
     allowed_type = {TEXT, NUMBER, FLOAT, PASSWORD, URL, DATE, TIME}
@@ -113,6 +129,20 @@ def input(label='', type=TEXT, *, valid_func=None, name=None, value=None, placeh
     if type == FLOAT:
         item_spec['type'] = TEXT
 
+    if action:
+        label, callback = action
+        task_id = get_current_task_id()
+
+        def _set_value(value):
+            msg = dict(command='update_input', task_id=task_id, spec={
+                'target_name': item_spec.get('name', 'data'),
+                'attributes': {'value': value}
+            })
+            get_current_session().send_task_command(msg)
+
+        callback_id = output_register_callback(lambda _: callback(_set_value))
+        item_spec['action'] = dict(label=label, callback_id=callback_id)
+
     return single_input(item_spec, valid_func, preprocess_func)
 
 
@@ -345,7 +375,7 @@ def actions(label='', buttons=None, name=None, help_text=None):
             if info['action'] == 'save_and_continue':
                 add_next()
 
-    处理复杂输入::
+    通过其他操作设置项值::
 
         def get_name(set_val):
             popup('Set name', [

+ 28 - 9
webiojs/src/models/input/input.ts

@@ -1,18 +1,27 @@
 import {InputItem} from "./base";
 import {Session} from "../../session";
 import {deep_copy} from "../../utils"
+import {state} from "../../state";
+
 
 const common_input_tpl = `
 <div class="form-group">
     {{#label}}<label for="{{id_name}}">{{label}}</label>{{/label}}
-    <input type="{{type}}" id="{{id_name}}" aria-describedby="{{id_name}}_help"  {{#list}}list="{{list}}"{{/list}} class="form-control" >
-    <datalist id="{{id_name}}-list">
-        {{#datalist}} 
-        <option>{{.}}</option> 
-        {{/datalist}}
-    </datalist>
-    <div class="invalid-feedback">{{invalid_feedback}}</div>  <!-- input 添加 is-invalid 类 -->
-    <div class="valid-feedback">{{valid_feedback}}</div> <!-- input 添加 is-valid 类 -->
+    <div class="input-group">
+        <input type="{{type}}" id="{{id_name}}" aria-describedby="{{id_name}}_action_btn"  {{#list}}list="{{list}}"{{/list}} class="form-control" >
+        <datalist id="{{id_name}}-list">
+            {{#datalist}} 
+            <option>{{.}}</option> 
+            {{/datalist}}
+        </datalist>
+        {{#action}} 
+        <div class="input-group-append">
+            <button class="btn btn-outline-secondary single-input-action-btn" type="button" id="{{id_name}}_action_btn" data-callbackid="{{callback_id}}">{{label}}</button>
+        </div>
+        {{/action}} 
+        <div class="invalid-feedback">{{invalid_feedback}}</div>
+        <div class="valid-feedback">{{valid_feedback}}</div>
+    </div>
     <small id="{{id_name}}_help" class="form-text text-muted">{{help_text}}</small>
 </div>`;
 
@@ -34,8 +43,17 @@ export class Input extends InputItem {
         let html = Mustache.render(common_input_tpl, spec);
 
         this.element = $(html);
-        let input_elem = this.element.find('#' + id_name);
 
+        this.element.find(`#${id_name}_action_btn`).on('click', function (e) {
+            let btn = $(this);
+            state.CurrentSession.send_message({
+                event: "callback",
+                task_id: btn.data('callbackid') as string,
+                data: null
+            });
+        });
+
+        let input_elem = this.element.find('#' + id_name);
         // blur事件时,发送当前值到服务器
         input_elem.on("blur", (e) => {
             this.send_value_listener(this, e)
@@ -43,6 +61,7 @@ export class Input extends InputItem {
 
         // 将额外的html参数加到input标签上
         const ignore_keys = {
+            'action': '',
             'type': '',
             'label': '',
             'invalid_feedback': '',