浏览代码

add `reset` and `cancel` button types in `input.actions()`

wangweimin 5 年之前
父节点
当前提交
43c6d1341c
共有 4 个文件被更改,包括 76 次插入19 次删除
  1. 7 1
      docs/spec.rst
  2. 19 5
      pywebio/html/js/pywebio.js
  3. 46 12
      pywebio/input.py
  4. 4 1
      pywebio/io_ctrl.py

+ 7 - 1
docs/spec.rst

@@ -116,7 +116,7 @@ input_group:
 
 
 * actions
 * actions
 
 
-  * buttons: 选项列表。``{label:选项标签, value:选项值, [disabled:是否禁止选择]}``
+  * buttons: 选项列表。``{label:选项标签, value:选项值, [type: 按钮类型 'submit'/'reset'/'cancel'], [disabled:是否禁止选择]}``
 
 
 
 
 
 
@@ -238,3 +238,9 @@ from_submit:
 
 
 事件 ``data`` 字段为表单 * ``name`` -> 表单值* 的字典
 事件 ``data`` 字段为表单 * ``name`` -> 表单值* 的字典
 
 
+from_cancel:
+^^^^^^^^^^^^^^^
+取消输入表单
+
+事件 ``data`` 字段为 ``None``
+

+ 19 - 5
pywebio/html/js/pywebio.js

@@ -777,7 +777,7 @@
     function ButtonsController(webio_session, task_id, spec) {
     function ButtonsController(webio_session, task_id, spec) {
         FormItemController.apply(this, arguments);
         FormItemController.apply(this, arguments);
 
 
-        this.last_checked_value = null;  // 上次点击按钮的value
+        this.submit_value = null;  // 提交表单时按钮组的value
         this.create_element();
         this.create_element();
     }
     }
 
 
@@ -787,7 +787,7 @@
 <div class="form-group">
 <div class="form-group">
     {{#label}}<label>{{label}}</label>  <br> {{/label}} 
     {{#label}}<label>{{label}}</label>  <br> {{/label}} 
     {{#buttons}}
     {{#buttons}}
-    <button type="submit" value="{{value}}" aria-describedby="{{name}}_help" {{#disabled}}disabled{{/disabled}} class="btn btn-primary">{{label}}</button>
+    <button type="{{btn_type}}" data-type="{{type}}" value="{{value}}" aria-describedby="{{name}}_help" {{#disabled}}disabled{{/disabled}} class="btn btn-primary">{{label}}</button>
     {{/buttons}}
     {{/buttons}}
     <div class="invalid-feedback">{{invalid_feedback}}</div>  <!-- input 添加 is-invalid 类 -->
     <div class="invalid-feedback">{{invalid_feedback}}</div>  <!-- input 添加 is-invalid 类 -->
     <div class="valid-feedback">{{valid_feedback}}</div> <!-- input 添加 is-valid 类 -->
     <div class="valid-feedback">{{valid_feedback}}</div> <!-- input 添加 is-valid 类 -->
@@ -795,14 +795,28 @@
 </div>`;
 </div>`;
 
 
     ButtonsController.prototype.create_element = function () {
     ButtonsController.prototype.create_element = function () {
+        for (var b of this.spec.buttons) b['btn_type'] = b.type === "submit" ? "submit" : "button";
+
         const html = Mustache.render(buttons_tpl, this.spec);
         const html = Mustache.render(buttons_tpl, this.spec);
         this.element = $(html);
         this.element = $(html);
 
 
-        // todo:是否有必要监听click事件,因为点击后即提交了表单
         var that = this;
         var that = this;
         this.element.find('button').on('click', function (e) {
         this.element.find('button').on('click', function (e) {
             var btn = $(this);
             var btn = $(this);
-            that.last_checked_value = btn.val();
+            if (btn.data('type') === 'submit') {
+                that.submit_value = btn.val();
+                // 不可以使用 btn.parents('form').submit(), 会导致input 的required属性失效
+            } else if (btn.data('type') === 'reset') {
+                btn.parents('form').trigger("reset");
+            } else if (btn.data('type') === 'cancel') {
+                that.webio_session.send_message({
+                    event: "from_cancel",
+                    task_id: that.task_id,
+                    data: null
+                });
+            } else {
+                console.error("`actions` input: unknown button type '%s'", btn.data('type'));
+            }
         });
         });
     };
     };
 
 
@@ -821,7 +835,7 @@
     };
     };
 
 
     ButtonsController.prototype.get_value = function () {
     ButtonsController.prototype.get_value = function () {
-        return this.last_checked_value;
+        return this.submit_value;
     };
     };
 
 
     function FileInputController(webio_session, task_id, spec) {
     function FileInputController(webio_session, task_id, spec) {

+ 46 - 12
pywebio/input.py

@@ -249,20 +249,28 @@ def _parse_action_buttons(buttons):
     :param label:
     :param label:
     :param actions: action 列表
     :param actions: action 列表
     action 可用形式:
     action 可用形式:
-        {label:, value:, [disabled:]}
-        (label, value, [disabled])
-        value 单值,label等于value
-    :return:
+
+        * dict: ``{label:选项标签, value:选项值, [type: 按钮类型], [disabled:是否禁止选择]}``
+        * tuple or list: ``(label, value, [type], [disabled])``
+        * 单值: 此时label和value使用相同的值
+
+    :return: 规格化后的 buttons
     """
     """
     act_res = []
     act_res = []
     for act in buttons:
     for act in buttons:
         if isinstance(act, Mapping):
         if isinstance(act, Mapping):
-            assert 'value' in act and 'label' in act, 'actions item must have value and label key'
+            assert 'label' in act, 'actions item must have label key'
+            assert 'value' in act or act.get('type', 'submit') != 'submit', \
+                'actions item must have value key for submit type'
         elif isinstance(act, (list, tuple)):
         elif isinstance(act, (list, tuple)):
-            assert len(act) in (2, 3), 'actions item format error'
-            act = dict(zip(('label', 'value', 'disabled'), act))
+            assert len(act) in (2, 3, 4), 'actions item format error'
+            act = dict(zip(('label', 'value', 'type', 'disabled'), act))
         else:
         else:
             act = dict(value=act, label=act)
             act = dict(value=act, label=act)
+
+        act.setdefault('type', 'submit')
+        assert act['type'] in ('submit', 'reset', 'cancel'), \
+            "submit type muse be 'submit' or 'reset' or 'cancel', not %r" % act['type']
         act_res.append(act)
         act_res.append(act)
 
 
     return act_res
     return act_res
@@ -270,18 +278,43 @@ def _parse_action_buttons(buttons):
 
 
 def actions(label='', buttons=None, name=None, help_text=None):
 def actions(label='', buttons=None, name=None, help_text=None):
     r"""按钮选项。
     r"""按钮选项。
-    在浏览器上显示为一组按钮,与其他输入组件不同,用户点击按钮后会立即将整个表单提交,而其他输入组件则需要手动点击表单的"提交"按钮。
+    在浏览器上显示为一组按钮,与其他输入组件不同,用户点击按钮后会立即将整个表单提交(除非设置按钮的 ``type='reset'`` ),
+    而其他输入组件则需要手动点击表单的"提交"按钮。
 
 
     当 ``actions()`` 作为 `input_group()` 的 ``inputs`` 中最后一个输入项时,表单默认的提交按钮会被当前 ``actions()`` 替换。
     当 ``actions()`` 作为 `input_group()` 的 ``inputs`` 中最后一个输入项时,表单默认的提交按钮会被当前 ``actions()`` 替换。
 
 
     :param list buttons: 选项列表。列表项的可用形式有:
     :param list buttons: 选项列表。列表项的可用形式有:
 
 
-        * dict: ``{label:选项标签, value:选项值, [disabled:是否禁止选择]}``
-        * tuple or list: ``(label, value, [disabled])``
+        * dict: ``{label:选项标签, value:选项值, [type: 按钮类型], [disabled:是否禁止选择]}`` .
+          若 ``type='reset'/'cancel'`` 可省略 ``value``
+        * tuple or list: ``(label, value, [type], [disabled])``
         * 单值: 此时label和value使用相同的值
         * 单值: 此时label和value使用相同的值
 
 
+       ``type`` 可选值为:
+
+        * ``'submit'`` : 点击按钮后,将整个表单提交。 ``'submit'`` 为 ``type`` 的默认值
+        * ``'cancel'`` : 取消输入。点击按钮后, ``actions()`` 将直接返回 ``None``
+        * ``'reset'`` : 点击按钮后,将整个表单重置,输入项将变为初始状态。
+          注意:点击 ``type=reset`` 的按钮后,并不会提交表单, ``actions()`` 调用也不会返回
+
     :param - label, name, help_text: 与 `input` 输入函数的同名参数含义一致
     :param - label, name, help_text: 与 `input` 输入函数的同名参数含义一致
-    :return: 用户点击的按钮的值
+    :return: 若用户点击当前按钮组中的某一按钮而触发表单提交,返回用户点击的按钮的值。
+       若用户点击 ``type=cancel`` 按钮或通过其它方式提交表单,则返回 ``None``
+
+    使用示例::
+
+        info = input_group('Add user', [
+            input('username', type=TEXT, name='username', required=True),
+            input('password', type=PASSWORD, name='password', required=True),
+            actions('actions', [
+                {'label': '提交', 'value': 'submit'},
+                {'label': '重置', 'type': 'reset'},
+                {'label': '取消', 'type': 'cancel'},
+            ], name='action', help_text='actions'),
+        ])
+        if info is not None:
+            save_user(info['username'], info['password'])
+
     """
     """
     assert buttons is not None, ValueError('Required `buttons` parameter in actions()')
     assert buttons is not None, ValueError('Required `buttons` parameter in actions()')
 
 
@@ -292,7 +325,8 @@ def actions(label='', buttons=None, name=None, help_text=None):
     return single_input(item_spec, valid_func, lambda d: d)
     return single_input(item_spec, valid_func, lambda d: d)
 
 
 
 
-def file_upload(label='', accept=None, name=None, placeholder='Choose file', required=None, help_text=None, **other_html_attrs):
+def file_upload(label='', accept=None, name=None, placeholder='Choose file', required=None, help_text=None,
+                **other_html_attrs):
     r"""文件上传。
     r"""文件上传。
 
 
     :param accept: 单值或列表, 表示可接受的文件类型。单值或列表项支持的形式有:
     :param accept: 单值或列表, 表示可接受的文件类型。单值或列表项支持的形式有:

+ 4 - 1
pywebio/io_ctrl.py

@@ -4,10 +4,10 @@
 
 
 """
 """
 import logging
 import logging
+from functools import wraps
 
 
 from .session import get_session_implement, CoroutineBasedSession, get_current_task_id, get_current_session
 from .session import get_session_implement, CoroutineBasedSession, get_current_task_id, get_current_session
 from .utils import run_as_function, to_coroutine
 from .utils import run_as_function, to_coroutine
-from functools import wraps
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -138,6 +138,9 @@ def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs):
 
 
             if all_valid:
             if all_valid:
                 break
                 break
+        elif event_name == 'from_cancel':
+            data = None
+            break
         else:
         else:
             logger.warning("Unhandled Event: %s", event)
             logger.warning("Unhandled Event: %s", event)