Ver Fonte

feat: `actions()` and `put_buttons()`'s buttons' value、 `radio()` and `select()` and `checkbox()`'s options' value accept json-serializable obj

wangweimin há 4 anos atrás
pai
commit
13fce7e7fd

+ 5 - 5
pywebio/input.py

@@ -268,7 +268,7 @@ def _parse_select_options(options):
             opt = dict(zip(('label', 'value', 'selected', 'disabled'), opt))
         else:
             opt = dict(value=opt, label=opt)
-        opt['value'] = str(opt['value'])
+        opt['value'] = opt['value']
         opts_res.append(opt)
 
     return opts_res
@@ -298,7 +298,7 @@ def select(label='', options=None, *, multiple=None, valid_func=None, name=None,
 
         注意:
 
-        1. options 中的 value 最终会转换成字符串。 select 返回值也是字符串(或字符串列表)
+        1. ``options`` 中的 ``value`` 可以为任意可Json序列化对象
         2. 若 ``multiple`` 选项不为 ``True`` 则可选项列表最多仅能有一项的 ``selected`` 为 ``True``。
 
     :param bool multiple: 是否可以多选. 默认单选
@@ -308,7 +308,7 @@ def select(label='', options=None, *, multiple=None, valid_func=None, name=None,
     :type value: list or str
     :param bool required: 是否至少选择一项
     :param - label, valid_func, name, help_text, other_html_attrs: 与 `input` 输入函数的同名参数含义一致
-    :return: 字符串/字符串列表。如果 ``multiple=True`` 时,返回用户选中的 options 中的值的列表;不设置 ``multiple`` 时,返回用户选中的 options 中的值
+    :return: 如果 ``multiple=True`` 时,返回用户选中的 ``options`` 中的值的列表;不设置 ``multiple`` 时,返回用户选中的 ``options`` 中的值
     """
     assert options is not None, ValueError('Required `options` parameter in select()')
 
@@ -354,7 +354,7 @@ def radio(label='', options=None, *, inline=None, valid_func=None, name=None, va
        你也可以通过设置 ``options`` 列表项中的 ``selected`` 字段来设置默认选中选项。
     :param bool required: 是否至少选择一项
     :param - label, valid_func, name, help_text, other_html_attrs: 与 `input` 输入函数的同名参数含义一致
-    :return: 用户选中的选项的值(字符串)
+    :return: 用户选中的选项的值
     """
     assert options is not None, ValueError('Required `options` parameter in radio()')
 
@@ -417,7 +417,7 @@ def actions(label='', buttons=None, name=None, help_text=None):
         * tuple or list: ``(label, value, [type], [disabled])``
         * 单值: 此时label和value使用相同的值
 
-       其中 ``type`` 可选值为:
+       其中, ``value`` 可以为任意可json序列化的对象。 ``type`` 可选值为:
 
         * ``'submit'`` : 点击按钮后,将整个表单提交,最终表单中本项的值为被点击按钮的 ``value`` 值。 ``'submit'`` 为 ``type`` 的默认值
         * ``'callback'`` : 点击按钮后,将运行一个回调,回调函数通过 ``value`` 字段指定,可以在回调函数内设置表单中本项的值。具体用法见下文。

+ 2 - 0
pywebio/output.py

@@ -543,6 +543,8 @@ def put_buttons(buttons, onclick, small=None, link_style=False, scope=Scope.Curr
         * tuple or list: ``(label, value)``
         * 单值: 此时label和value使用相同的值
 
+        其中, ``value`` 可以为任意可json序列化的对象
+
     :type onclick: Callable / list
     :param onclick: 按钮点击回调函数. ``onclick`` 可以是函数或者函数组成的列表.
 

+ 2 - 2
test/template.py

@@ -690,13 +690,13 @@ def test_input(browser: Chrome, enable_percy=False):
     # browser. find_element_by_css_selector('[name="radio"]'). send_keys("name")
     browser.find_element_by_name('file_upload').send_keys(path.join(here_dir, 'assets', 'helloworld.txt'))
 
-    browser.execute_script("arguments[0].click();", browser.find_element_by_css_selector('button[value="submit2"]'))
+    browser.execute_script("$('form button').eq(1).click()")
     time.sleep(1)
     enable_percy and percySnapshot(browser=browser, name='input group all invalid')
 
     browser.find_element_by_name('password').clear()
     browser.find_element_by_name('password').send_keys("123")
-    browser.execute_script("arguments[0].click();", browser.find_element_by_css_selector('button[value="submit2"]'))
+    browser.execute_script("$('form button').eq(1).click()")
     time.sleep(0.5)
     enable_percy and percySnapshot(browser=browser, name='input group all submit')
 

+ 7 - 4
webiojs/src/models/input/actions.ts

@@ -6,7 +6,7 @@ const buttons_tpl = `
 <div class="form-group">
     {{#label}}<label>{{label}}</label>  <br> {{/label}} 
     {{#buttons}}
-    <button type="{{btn_type}}" data-type="{{type}}" value="{{value}}" aria-describedby="{{name}}_help" {{#disabled}}disabled data-pywebio-disabled{{/disabled}} class="btn btn-primary">{{label}}</button>
+    <button type="{{btn_type}}" data-type="{{type}}" aria-describedby="{{name}}_help" {{#disabled}}disabled data-pywebio-disabled{{/disabled}} class="btn btn-primary">{{label}}</button>
     {{/buttons}}
     <div class="invalid-feedback">{{invalid_feedback}}</div>  <!-- input 添加 is-invalid 类 -->
     <div class="valid-feedback">{{valid_feedback}}</div> <!-- input 添加 is-valid 类 -->
@@ -27,6 +27,9 @@ export class Actions extends InputItem {
 
         const html = Mustache.render(buttons_tpl, this.spec);
         this.element = $(html);
+        let btns = this.element.find('button');
+        for(let idx =0; idx<this.spec.buttons.length; idx++)
+            btns.eq(idx).val(JSON.stringify(this.spec.buttons[idx].value));
 
         for (let btn of this.spec.buttons){
             if (btn.type=='callback'){
@@ -40,7 +43,7 @@ export class Actions extends InputItem {
         this.element.find('button').on('click', function (e) {
             let btn = $(this);
             if (btn.data('type') === 'submit') {
-                that.submit_value = btn.val() as string;
+                that.submit_value = JSON.parse(btn.val() as string);
                 // 不可以使用 btn.parents('form').submit(), 会导致input 的required属性失效
             } else if (btn.data('type') === 'reset') {
                 btn.parents('form').trigger("reset");
@@ -53,7 +56,7 @@ export class Actions extends InputItem {
             } else if (btn.data('type') === 'callback') {
                 state.CurrentSession.send_message({
                     event: "callback",
-                    task_id: btn.val() as string,
+                    task_id: JSON.parse(btn.val() as string),
                     data: null
                 });
             } else {
@@ -75,7 +78,7 @@ export class Actions extends InputItem {
         let idx = -1;
         if ('target_value' in spec) {
             this.element.find('button').each(function (index) {
-                if ($(this).val() === spec.target_value) {
+                if (JSON.parse($(this).val() as string) === spec.target_value) {
                     idx = index;
                     return false;
                 }

+ 10 - 4
webiojs/src/models/input/checkbox_radio.ts

@@ -8,7 +8,7 @@ const checkbox_radio_tpl = `
     {{#inline}}<br>{{/inline}}
     {{#options}}
     <div class="form-check {{#inline}}form-check-inline{{/inline}}">
-        <input type="{{type}}" id="{{id_name_prefix}}-{{idx}}" name="{{name}}" value="{{value}}" {{#selected}}checked{{/selected}} {{#disabled}}disabled{{/disabled}} class="form-check-input">
+        <input type="{{type}}" id="{{id_name_prefix}}-{{idx}}" name="{{name}}" {{#selected}}checked{{/selected}} {{#disabled}}disabled{{/disabled}} class="form-check-input">
         <label class="form-check-label" for="{{id_name_prefix}}-{{idx}}">
             {{label}}
         </label>
@@ -37,6 +37,10 @@ export class CheckboxRadio extends InputItem {
         let elem = $(html);
         this.element = elem;
 
+        let inputs = elem.find('input');
+        for (let idx = 0; idx < spec.options.length; idx++)
+            inputs.eq(idx).val(JSON.stringify(spec.options[idx].value));
+
         const ignore_keys = {'value': '', 'label': '', 'selected': ''};
         for (let idx = 0; idx < this.spec.options.length; idx++) {
             let input_elem = elem.find('#' + id_name_prefix + '-' + idx);
@@ -69,14 +73,16 @@ export class CheckboxRadio extends InputItem {
 
     get_value(): any {
         if (this.spec.type === 'radio') {
-            return this.element.find('input:checked').val() || '';
+            let raw_val = this.element.find('input:checked').val() || 'null';
+            return JSON.parse(raw_val as string);
         } else {
             let value_arr = this.element.find('input').serializeArray();
-            let res: string[] = [];
+            let res: any[] = [];
             let that = this;
             $.each(value_arr, function (idx, val) {
                 if (val.name === that.spec.name)
-                    res.push(val.value);
+                    res.push(JSON.parse(val.value as string));
+                console.log(JSON.parse(val.value as string), typeof JSON.parse(val.value as string));
             });
             return res;
         }

+ 14 - 3
webiojs/src/models/input/select.ts

@@ -8,7 +8,7 @@ const select_input_tpl = `
     {{#label}}<label for="{{id_name}}">{{label}}</label>{{/label}}
     <select id="{{id_name}}" aria-describedby="{{id_name}}_help" class="form-control" {{#multiple}}multiple{{/multiple}}>
         {{#options}}
-        <option value="{{value}}" {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}}>{{label}}</option>
+        <option {{#selected}}selected{{/selected}} {{#disabled}}disabled{{/disabled}}>{{label}}</option>
         {{/options}}
     </select>
     <div class="invalid-feedback">{{invalid_feedback}}</div>
@@ -28,12 +28,15 @@ export class Select extends InputItem {
         const id_name = spec.name + '-' + Math.floor(Math.random() * Math.floor(9999));
         spec['id_name'] = id_name;
 
-
         let html = Mustache.render(select_input_tpl, spec);
 
         this.element = $(html);
         let input_elem = this.element.find('#' + id_name);
 
+        let opts = input_elem.find('option');
+        for (let idx = 0; idx < spec.options.length; idx++)
+            opts.eq(idx).val(JSON.stringify(spec.options[idx].value));
+
         // blur事件时,发送当前值到服务器
         input_elem.on("blur", (e) => {
             this.send_value_listener(this, e)
@@ -65,7 +68,15 @@ export class Select extends InputItem {
     }
 
     get_value(): any {
-        return this.element.find('select').val();
+        let raw_val = this.element.find('select').val();
+        if (this.spec.multiple) {
+            let res: any[] = [];
+            for (let i of (raw_val as string[]))
+                res.push(JSON.parse(i));
+            return res;
+        } else {
+            return JSON.parse(raw_val as string);
+        }
     }
 }
 

+ 10 - 4
webiojs/src/models/output.ts

@@ -56,11 +56,17 @@ let Buttons = {
     handle_type: 'buttons',
     get_element: function (spec: any) {
         const btns_tpl = `<div>{{#buttons}}
-                             <button value="{{value}}" onclick="WebIO.DisplayAreaButtonOnClick(this, '{{callback_id}}')" class="btn {{btn_class}}{{#small}} btn-sm{{/small}}">{{label}}</button> 
+                             <button onclick="WebIO.DisplayAreaButtonOnClick(this, '{{callback_id}}')" class="btn {{btn_class}}{{#small}} btn-sm{{/small}}">{{label}}</button> 
                           {{/buttons}}</div>`;
         spec.btn_class = spec.link ? "btn-link" : "btn-primary";
         let html = Mustache.render(btns_tpl, spec);
-        return $(html);
+        let elem =  $(html);
+
+        let btns = elem.find('button');
+        for(let idx =0; idx< spec.buttons.length; idx++)
+            btns.eq(idx).val(JSON.stringify(spec.buttons[idx].value));
+
+        return elem;
     }
 };
 
@@ -69,11 +75,11 @@ export function DisplayAreaButtonOnClick(this_ele: HTMLElement, callback_id: str
     if (state.CurrentSession === null)
         return console.error("can't invoke DisplayAreaButtonOnClick when WebIOController is not instantiated");
 
-    let val = $(this_ele).val();
+    let val = $(this_ele).val() as string;
     state.CurrentSession.send_message({
         event: "callback",
         task_id: callback_id,
-        data: val
+        data: JSON.parse(val)
     });
 }