Browse Source

前端支持button控件

wangweimin 5 years ago
parent
commit
f2599aea82
6 changed files with 143 additions and 50 deletions
  1. 4 4
      doc/spec.md
  2. 10 2
      test.py
  3. 20 6
      wsrepl/html/bs.html
  4. 88 22
      wsrepl/html/js/form.js
  5. 20 15
      wsrepl/interact.py
  6. 1 1
      wsrepl/ioloop.py

+ 4 - 4
doc/spec.md

@@ -50,10 +50,10 @@ ref https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/textarea
 <button>
 <button>
 ref https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button
 ref https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button
 
 
-type=buttons
+type=actions
     label
     label
     name
     name
-    actions 字典列表 {*value:, *label:, disabled}
+    buttons 字典列表 {*value:, *label:, disabled}
 
 
 input_group:
 input_group:
     label: # todo change to label
     label: # todo change to label
@@ -64,7 +64,7 @@ input_group:
 控制类指令
 控制类指令
 update_input:
 update_input:
     target_name: input主键name
     target_name: input主键name
-    ~target_value:str 用于checkbox, radio 过滤input 
+    ~target_value:str 用于checkbox, radio, button 过滤input 
     attributes: {
     attributes: {
         valid_status: bool 输入值的有效性,通过/不通过
         valid_status: bool 输入值的有效性,通过/不通过
         value:
         value:
@@ -92,7 +92,7 @@ output:
     数据项2:
     数据项2:
 
 
 input_event
 input_event
-    event_name: on_blur
+    event_name: blur, click
     name:
     name:
     value:
     value:
 
 

+ 10 - 2
test.py

@@ -12,6 +12,13 @@ from tornado.gen import sleep
 def say_hello():
 def say_hello():
     # 向用户输出文字
     # 向用户输出文字
     text_print("Welcome!!!")
     text_print("Welcome!!!")
+    res = yield from actions('Action button', [
+        {'value': '1', 'label': 'One', 'disabled': False},
+        {'value': '2', 'label': 'Two', 'disabled': False},
+        {'value': '3', 'label': 'Three', 'disabled': True},
+    ])
+    text_print('Your input:%s' % res)
+
     res = yield from select('This is select input', [
     res = yield from select('This is select input', [
         {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
         {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
         {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
         {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
@@ -30,14 +37,15 @@ def say_hello():
         {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
         {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
         {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
         {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
         {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
         {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
-    ], type=RADIO, multiple=True)
+    ], type=RADIO)
     text_print('Your input:%s' % res)
     text_print('Your input:%s' % res)
 
 
     res = yield from select('This is CHECKBOX input', [
     res = yield from select('This is CHECKBOX input', [
         {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
         {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
         {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
         {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
         {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
         {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
-    ], type=CHECKBOX, multiple=True)
+    ], type=CHECKBOX)
+
     text_print('Your input:%s' % res)
     text_print('Your input:%s' % res)
 
 
     res = yield from input('This is single input')
     res = yield from input('This is single input')

+ 20 - 6
wsrepl/html/bs.html

@@ -44,6 +44,7 @@
             <input type="checkbox" class="form-check-input" id="exampleCheck1">
             <input type="checkbox" class="form-check-input" id="exampleCheck1">
             <label class="form-check-label" for="exampleCheck1">Check me out</label>
             <label class="form-check-label" for="exampleCheck1">Check me out</label>
         </div>
         </div>
+
         <button type="submit" class="btn btn-primary">Submit</button>
         <button type="submit" class="btn btn-primary">Submit</button>
     </form>
     </form>
 
 
@@ -51,7 +52,17 @@
     <form id="f">
     <form id="f">
         <div class="form-group">
         <div class="form-group">
             <label for="num_input">Input</label>
             <label for="num_input">Input</label>
-            <input type="number" class="form-control"  id="num_input">
+            <input type="number" class="form-control" id="num_input">
+        </div>
+        <div class="form-group">
+            <label for="submit_input">submit_input</label> <br>
+            <input type="submit" class="btn btn-primary" id="submit_input">
+            <input type="submit" class="btn btn-primary" value="哈哈">
+            <button value="s" aria-describedby="emailHelp" class="btn btn-primary">No Submit</button>
+            <button type="submit" name="submit" value="s" aria-describedby="emailHelp" class="btn btn-primary">Submit</button>
+            <div class="invalid-feedback">invalid-feedback.</div>  <!-- input 添加 is-invalid 类 -->
+            <div class="valid-feedback">valid-feedback!</div> <!-- input 添加 is-valid 类 -->
+            <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
         </div>
         </div>
         <div class="form-group">
         <div class="form-group">
             <label for="exampleFormControlSelect1">Select</label>
             <label for="exampleFormControlSelect1">Select</label>
@@ -128,7 +139,8 @@
                 </label>
                 </label>
             </div>
             </div>
             <div class="form-check form-check-inline">
             <div class="form-check form-check-inline">
-                <input class="form-check-input is-invalid" type="radio" name="exampleRadios1" id="exampleRadios22" value="option2">
+                <input class="form-check-input is-invalid" type="radio" name="exampleRadios1" id="exampleRadios22"
+                       value="option2">
                 <label class="form-check-label" for="exampleRadios22">
                 <label class="form-check-label" for="exampleRadios22">
                     Option two
                     Option two
                 </label>
                 </label>
@@ -140,14 +152,16 @@
         <div class="form-group">
         <div class="form-group">
             <label>Checkout</label>
             <label>Checkout</label>
             <div class="form-check">
             <div class="form-check">
-                <input class="form-check-input is-invalid" type="checkbox" value="" id="defaultCheck1" name="defaultCheck1">
+                <input class="form-check-input is-invalid" type="checkbox" value="" id="defaultCheck1"
+                       name="defaultCheck1">
                 <label class="form-check-label" for="defaultCheck1">
                 <label class="form-check-label" for="defaultCheck1">
                     Option one is this and that—be sure to include why it's great
                     Option one is this and that—be sure to include why it's great
                 </label>
                 </label>
 
 
             </div>
             </div>
             <div class="form-check">
             <div class="form-check">
-                <input class="form-check-input is-invalid" type="checkbox" value="" id="defaultCheck2" name="defaultCheck1">
+                <input class="form-check-input is-invalid" type="checkbox" value="" id="defaultCheck2"
+                       name="defaultCheck1">
                 <label class="form-check-label" for="defaultCheck2">
                 <label class="form-check-label" for="defaultCheck2">
                     Option two is disabled
                     Option two is disabled
                 </label>
                 </label>
@@ -159,13 +173,13 @@
         <div class="form-group">
         <div class="form-group">
             <label>Checkout inline</label> <br>
             <label>Checkout inline</label> <br>
             <div class="form-check form-check-inline">
             <div class="form-check form-check-inline">
-                <input class="form-check-input" type="checkbox" value="" id="defaultCheck21"  name="defaultCheck2">
+                <input class="form-check-input" type="checkbox" value="" id="defaultCheck21" name="defaultCheck2">
                 <label class="form-check-label" for="defaultCheck21">
                 <label class="form-check-label" for="defaultCheck21">
                     Option one
                     Option one
                 </label>
                 </label>
             </div>
             </div>
             <div class="form-check form-check-inline">
             <div class="form-check form-check-inline">
-                <input class="form-check-input" type="checkbox" value="" id="defaultCheck22"  name="defaultCheck2">
+                <input class="form-check-input" type="checkbox" value="" id="defaultCheck22" name="defaultCheck2">
                 <label class="form-check-label" for="defaultCheck22">
                 <label class="form-check-label" for="defaultCheck22">
                     Option two
                     Option two
                 </label>
                 </label>

+ 88 - 22
wsrepl/html/js/form.js

@@ -119,7 +119,7 @@
                 console.log('开:%s', ctrl.spec.label);
                 console.log('开:%s', ctrl.spec.label);
                 return ctrl.element.show(200, function () {
                 return ctrl.element.show(200, function () {
                     // 有时候autofocus属性不生效,手动激活一下
                     // 有时候autofocus属性不生效,手动激活一下
-                    $('input[autofocus]').focus();
+                    $('[autofocus]').focus();
                 });
                 });
             }
             }
             this.form_ctrls.move_to_top(coro_id);
             this.form_ctrls.move_to_top(coro_id);
@@ -130,7 +130,7 @@
                 var t = that.form_ctrls.get_top();
                 var t = that.form_ctrls.get_top();
                 if (t) t[t.length - 1].element.show(200, function () {
                 if (t) t[t.length - 1].element.show(200, function () {
                     // 有时候autofocus属性不生效,手动激活一下
                     // 有时候autofocus属性不生效,手动激活一下
-                    $('input[autofocus]').focus();
+                    $('[autofocus]').focus();
                 });
                 });
             });
             });
         };
         };
@@ -193,7 +193,7 @@
                         deleted.element.remove();
                         deleted.element.remove();
                         var t = that.form_ctrls.get_top();
                         var t = that.form_ctrls.get_top();
                         if (t) t[t.length - 1].element.show(200, function () {
                         if (t) t[t.length - 1].element.show(200, function () {
-                            $('input[autofocus]').focus();
+                            $('[autofocus]').focus();
                         });
                         });
                     });
                     });
                 } else {
                 } else {
@@ -220,11 +220,13 @@
         this.spec = spec;
         this.spec = spec;
 
 
         this.element = undefined;
         this.element = undefined;
-        this.input_controllers = {};  // name -> input_controller
+        this.name2input_controllers = {};  // name -> input_controller
 
 
         this.create_element();
         this.create_element();
     }
     }
 
 
+    FormController.prototype.input_controllers = [CommonInputController, CheckboxRadioController, ButtonsController];
+
     FormController.prototype.create_element = function () {
     FormController.prototype.create_element = function () {
         var tpl = `
         var tpl = `
         <div class="card" style="display: none">
         <div class="card" style="display: none">
@@ -244,16 +246,22 @@
         // 输入控件创建
         // 输入控件创建
         var body = this.element.find('.input-container');
         var body = this.element.find('.input-container');
         for (var idx in this.spec.inputs) {
         for (var idx in this.spec.inputs) {
-            var i = this.spec.inputs[idx];
-            var ctrl;
-            if (i.type in make_set(CommonInputController.prototype.accept_input_types)) {
-                ctrl = new CommonInputController(this.ws_client, this.coro_id, i);
-            } else if (i.type in make_set(CheckboxRadioController.prototype.accept_input_types)) {
-                ctrl = new CheckboxRadioController(this.ws_client, this.coro_id, i);
+            var input_spec = this.spec.inputs[idx];
+            var ctrl = undefined;
+            for (var i in this.input_controllers) {
+                var ctrl_cls = this.input_controllers[i];
+                // console.log(ctrl_cls, ctrl_cls.prototype.accept_input_types);
+                if (input_spec.type in make_set(ctrl_cls.prototype.accept_input_types)) {
+                    ctrl = new ctrl_cls(this.ws_client, this.coro_id, input_spec);
+                    break;
+                }
+            }
+            if (ctrl) {
+                this.name2input_controllers[input_spec.name] = ctrl;
+                body.append(ctrl.element);
+            } else {
+                console.error('Unvalid input type:%s', input_spec.type);
             }
             }
-
-            this.input_controllers[i.name] = ctrl;
-            body.append(ctrl.element);
         }
         }
 
 
         // 事件绑定
         // 事件绑定
@@ -261,7 +269,7 @@
         this.element.on('submit', 'form', function (e) {
         this.element.on('submit', 'form', function (e) {
             e.preventDefault(); // avoid to execute the actual submit of the form.
             e.preventDefault(); // avoid to execute the actual submit of the form.
             var data = {};
             var data = {};
-            $.each(that.input_controllers, (name, ctrl) => {
+            $.each(that.name2input_controllers, (name, ctrl) => {
                 data[name] = ctrl.get_value();
                 data[name] = ctrl.get_value();
             });
             });
             ws.send(JSON.stringify({
             ws.send(JSON.stringify({
@@ -273,11 +281,11 @@
     };
     };
 
 
     FormController.prototype.dispatch_ctrl_message = function (spec) {
     FormController.prototype.dispatch_ctrl_message = function (spec) {
-        if (!(spec.target_name in this.input_controllers)) {
+        if (!(spec.target_name in this.name2input_controllers)) {
             return console.error('Can\'t find input[name=%s] element in curr form!', spec.target_name);
             return console.error('Can\'t find input[name=%s] element in curr form!', spec.target_name);
         }
         }
 
 
-        this.input_controllers[spec.target_name].update_input(spec);
+        this.name2input_controllers[spec.target_name].update_input(spec);
     };
     };
 
 
 
 
@@ -295,8 +303,8 @@
                 coro_id: that.coro_id,
                 coro_id: that.coro_id,
                 data: {
                 data: {
                     event_name: e.type.toLowerCase(),
                     event_name: e.type.toLowerCase(),
-                    name: this_elem.attr('name'),
-                    value: this_elem.val()
+                    name: that.spec.name,
+                    value: that.get_value()
                 }
                 }
             }));
             }));
         };
         };
@@ -384,7 +392,14 @@
         input_elem.on('blur', this.send_value_listener);
         input_elem.on('blur', this.send_value_listener);
 
 
         // 将额外的html参数加到input标签上
         // 将额外的html参数加到input标签上
-        const ignore_keys = {'type': '', 'label': '', 'invalid_feedback': '', 'valid_feedback': '', 'help_text': '', 'options':''};
+        const ignore_keys = {
+            'type': '',
+            'label': '',
+            'invalid_feedback': '',
+            'valid_feedback': '',
+            'help_text': '',
+            'options': ''
+        };
         for (var key in this.spec) {
         for (var key in this.spec) {
             if (key in ignore_keys) continue;
             if (key in ignore_keys) continue;
             input_elem.attr(key, this.spec[key]);
             input_elem.attr(key, this.spec[key]);
@@ -414,7 +429,7 @@
     <label>{{label}}</label> {{#inline}}<br>{{/inline}}
     <label>{{label}}</label> {{#inline}}<br>{{/inline}}
     {{#options}}
     {{#options}}
     <div class="form-check {{#inline}}form-check-inline{{/inline}}">
     <div class="form-check {{#inline}}form-check-inline{{/inline}}">
-        <input type="{{type}}" id="{{id_name_prefix}}-{{idx}}" name="{{name}}" value="{{value}}" {{#selected}}checked{{/selected}} class="form-check-input">
+        <input type="{{type}}" id="{{id_name_prefix}}-{{idx}}" name="{{name}}" value="{{value}}" {{#selected}}checked{{/selected}} {{#disabled}}disabled{{/disabled}} class="form-check-input">
         <label class="form-check-label" for="{{id_name_prefix}}-{{idx}}">
         <label class="form-check-label" for="{{id_name_prefix}}-{{idx}}">
             {{label}}
             {{label}}
         </label>
         </label>
@@ -446,7 +461,7 @@
             // 将额外的html参数加到input标签上
             // 将额外的html参数加到input标签上
             for (var key in this.spec.options[idx]) {
             for (var key in this.spec.options[idx]) {
                 if (key in ignore_keys) continue;
                 if (key in ignore_keys) continue;
-                input_elem.attr(key, this.spec[key]);
+                input_elem.attr(key, this.spec.options[idx][key]);
             }
             }
         }
         }
     };
     };
@@ -456,7 +471,7 @@
         var idx = -1;
         var idx = -1;
         if ('target_value' in spec) {
         if ('target_value' in spec) {
             this.element.find('input').each(function (index) {
             this.element.find('input').each(function (index) {
-                if ($(this).val() == spec.target_value) {
+                if ($(this).val() === spec.target_value) {
                     idx = index;
                     idx = index;
                     return false;
                     return false;
                 }
                 }
@@ -480,6 +495,57 @@
         }
         }
     };
     };
 
 
+    function ButtonsController(ws_client, coro_id, spec) {
+        FormItemController.apply(this, arguments);
+
+        this.last_checked_value = undefined;  // 上次点击按钮的value
+        this.create_element();
+    }
+
+    ButtonsController.prototype.accept_input_types = ["actions"];
+
+    const buttons_tpl = `
+<div class="form-group">
+    <label>{{label}}</label> <br>
+    {{#buttons}}
+    <button type="submit" value="{{value}}" aria-describedby="{{name}}_help" {{#disabled}}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 类 -->
+    <small id="{{name}}_help" class="form-text text-muted">{{help_text}}</small>
+</div>`;
+
+    ButtonsController.prototype.create_element = function () {
+        var spec = deep_copy(this.spec);
+        const html = Mustache.render(buttons_tpl, this.spec);
+        this.element = $(html);
+
+        // todo:是否有必要监听click事件,因为点击后即提交了表单
+        var that = this;
+        this.element.find('button').on('click', function (e) {
+            var btn = $(this);
+            that.last_checked_value = btn.val();
+            that.send_value_listener.apply(this, arguments);
+        });
+    };
+
+    ButtonsController.prototype.update_input = function (spec) {
+        var attributes = spec.attributes;
+        var idx = -1;
+        if ('target_value' in spec) {
+            this.element.find('button').each(function (index) {
+                if ($(this).val() === spec.target_value) {
+                    idx = index;
+                    return false;
+                }
+            });
+        }
+        this.update_input_helper(idx, attributes);
+    };
+
+    ButtonsController.prototype.get_value = function () {
+        return this.last_checked_value;
+    };
 
 
 
 
     function WSREPLController(ws_client, output_container_elem, input_container_elem) {
     function WSREPLController(ws_client, output_container_elem, input_container_elem) {

+ 20 - 15
wsrepl/interact.py

@@ -45,7 +45,7 @@ def _input_event_handle(valid_funcs, whole_valid_func=None):
         event_name, event_data = event['event'], event['data']
         event_name, event_data = event['event'], event['data']
         if event_name == 'input_event':
         if event_name == 'input_event':
             input_event = event_data['event_name']
             input_event = event_data['event_name']
-            if input_event == 'on_blur':
+            if input_event == 'blur':
                 onblur_name = event_data['name']
                 onblur_name = event_data['name']
                 valid_func = valid_funcs.get(onblur_name)
                 valid_func = valid_funcs.get(onblur_name)
                 if valid_func is None:
                 if valid_func is None:
@@ -106,12 +106,12 @@ def _make_input_spec(label, type, name, valid_func=None, multiple=None, inline=N
         logger.warning('valid_func can\'t be used when type in (CHECKBOX, RADIO)')
         logger.warning('valid_func can\'t be used when type in (CHECKBOX, RADIO)')
 
 
     if inline is not None and type not in {CHECKBOX, RADIO}:
     if inline is not None and type not in {CHECKBOX, RADIO}:
-        logger.warning('inline 只能用于 CHECKBOX, RADIO type')
+        logger.warning('inline 只能用于 CHECKBOX, RADIO type, now type:%s', type)
     elif inline is not None:
     elif inline is not None:
         input_item['inline'] = inline
         input_item['inline'] = inline
 
 
     if multiple is not None and type != SELECT:
     if multiple is not None and type != SELECT:
-        logger.warning('multiple 参数只能用于SELECT type')
+        logger.warning('multiple 参数只能用于SELECT type, now type:%s', type)
     elif multiple is not None:
     elif multiple is not None:
         input_item['multiple'] = multiple
         input_item['multiple'] = multiple
 
 
@@ -136,12 +136,16 @@ def _make_input_spec(label, type, name, valid_func=None, multiple=None, inline=N
 
 
         input_item['options'] = opts_res
         input_item['options'] = opts_res
 
 
-    # todo spec参数中,为默认值的可以不发送
+    # todo spec参数中,为None的表示使用默认,不发送
+    for attr, val in list(input_item.items()):
+        if val is None:
+            del input_item[attr]
+
     return input_item
     return input_item
 
 
 
 
-def input(label, type=TEXT, *, valid_func=None, name='data', value='', placeholder='', required=False, readonly=False,
-          disabled=False, **other_html_attrs):
+def input(label, type=TEXT, *, valid_func=None, name='data', value='', placeholder='', required=None, readonly=None,
+          disabled=None, **other_html_attrs):
     input_kwargs = dict(locals())
     input_kwargs = dict(locals())
     input_kwargs['label'] = ''
     input_kwargs['label'] = ''
     input_kwargs['__name__'] = input.__name__
     input_kwargs['__name__'] = input.__name__
@@ -156,7 +160,7 @@ def input(label, type=TEXT, *, valid_func=None, name='data', value='', placehold
 
 
 
 
 def select(label, options, type=SELECT, *, multiple=None, valid_func=None, name='data', value='', placeholder='',
 def select(label, options, type=SELECT, *, multiple=None, valid_func=None, name='data', value='', placeholder='',
-           required=False, readonly=False, disabled=False, inline=None, **other_html_attrs):
+           required=None, readonly=None, disabled=None, inline=None, **other_html_attrs):
     """
     """
     参数值为None表示不指定,使用默认值
     参数值为None表示不指定,使用默认值
 
 
@@ -182,6 +186,7 @@ def select(label, options, type=SELECT, *, multiple=None, valid_func=None, name=
     input_kwargs = dict(locals())
     input_kwargs = dict(locals())
     input_kwargs['label'] = ''
     input_kwargs['label'] = ''
     input_kwargs['__name__'] = select.__name__
     input_kwargs['__name__'] = select.__name__
+    input_kwargs.setdefault('autofocus', True)  # 如果没有设置autofocus参数,则开启参数
 
 
     allowed_type = {CHECKBOX, RADIO, SELECT}
     allowed_type = {CHECKBOX, RADIO, SELECT}
     assert type in allowed_type, 'Input type not allowed.'
     assert type in allowed_type, 'Input type not allowed.'
@@ -190,7 +195,7 @@ def select(label, options, type=SELECT, *, multiple=None, valid_func=None, name=
     return data[name]
     return data[name]
 
 
 
 
-def _make_actions_input_spec(label, actions, name):
+def _make_actions_input_spec(label, buttons, name):
     """
     """
     :param label:
     :param label:
     :param actions: action 列表
     :param actions: action 列表
@@ -201,7 +206,7 @@ def _make_actions_input_spec(label, actions, name):
     :return:
     :return:
     """
     """
     act_res = []
     act_res = []
-    for act in actions:
+    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 'value' in act and 'label' in act, 'actions item must have value and label key'
         elif isinstance(act, Sequence):
         elif isinstance(act, Sequence):
@@ -211,11 +216,11 @@ def _make_actions_input_spec(label, actions, name):
             act = dict(value=act, label=act)
             act = dict(value=act, label=act)
         act_res.append(act)
         act_res.append(act)
 
 
-    input_item = dict(type='buttons', label=label, name=name, actions=actions)
+    input_item = dict(type='actions', label=label, name=name, buttons=buttons)
     return input_item
     return input_item
 
 
 
 
-def actions(label, actions, name='data'):
+def actions(label, buttons, name='data'):
     """
     """
     选择一个动作。UI为多个按钮,点击后会将整个表单提交
     选择一个动作。UI为多个按钮,点击后会将整个表单提交
     :param label:
     :param label:
@@ -232,8 +237,8 @@ def actions(label, actions, name='data'):
         ...
         ...
     ]
     ]
     """
     """
-    input_kwargs = dict(label='', actions=actions, name=name)
-    input_kwargs['__name__'] = select.__name__
+    input_kwargs = dict(label='', buttons=buttons, name=name)
+    input_kwargs['__name__'] = actions.__name__
     data = yield from input_group(label=label, inputs=[input_kwargs])
     data = yield from input_group(label=label, inputs=[input_kwargs])
     return data[name]
     return data[name]
 
 
@@ -248,7 +253,7 @@ def input_group(label, inputs, valid_func=None):
     make_spec_funcs = {
     make_spec_funcs = {
         actions.__name__: _make_actions_input_spec,
         actions.__name__: _make_actions_input_spec,
         input.__name__: _make_input_spec,
         input.__name__: _make_input_spec,
-        select.__name__: _make_input_spec
+        select.__name__: _make_input_spec,
     }
     }
 
 
     item_valid_funcs = {}
     item_valid_funcs = {}
@@ -262,7 +267,7 @@ def input_group(label, inputs, valid_func=None):
             func_name = input_g.__name__
             func_name = input_g.__name__
 
 
         input_name = input_kwargs['name']
         input_name = input_kwargs['name']
-        item_valid_funcs[input_name] = input_kwargs['valid_func']
+        item_valid_funcs[input_name] = input_kwargs.get('valid_func')
         input_item = make_spec_funcs[func_name](**input_kwargs)
         input_item = make_spec_funcs[func_name](**input_kwargs)
         spec_inputs.append(input_item)
         spec_inputs.append(input_item)
 
 

+ 1 - 1
wsrepl/ioloop.py

@@ -60,7 +60,7 @@ def start_ioloop(coro_func, port=8080):
 
 
         @coroutine
         @coroutine
         def on_message(self, message):
         def on_message(self, message):
-            print('on_message', message)
+            # print('on_message', message)
             # { event:, coro_id:, data: }
             # { event:, coro_id:, data: }
             data = json.loads(message)
             data = json.loads(message)
             coro_id = data['coro_id']
             coro_id = data['coro_id']