Преглед изворни кода

feat: add `slider()` input

wangweimin пре 4 година
родитељ
комит
aa629e3fbb
5 измењених фајлова са 116 додато и 4 уклоњено
  1. 7 0
      docs/spec.rst
  2. 26 1
      pywebio/input.py
  3. 11 2
      pywebio/pin.py
  4. 2 1
      webiojs/src/models/input/index.ts
  5. 70 0
      webiojs/src/models/input/slider.ts

+ 7 - 0
docs/spec.rst

@@ -146,6 +146,13 @@ Unique attributes of different input types:
    * max_size: The maximum size of a single file, in bytes.
    * max_total_size: The maximum size of all files, in bytes.
 
+* slider
+
+   * min_value: The minimum permitted value.
+   * max_value: The maximum permitted value.
+   * step: The stepping interval.
+   * float: If need return a float value
+
 update_input
 ^^^^^^^^^^^^^^^
 

+ 26 - 1
pywebio/input.py

@@ -46,6 +46,9 @@ Functions list
    * - `radio <pywebio.input.radio>`
      - Radio
 
+   * - `slider <pywebio.input.slider>`
+     - Slider
+
    * - `actions <pywebio.input.actions>`
      - Actions selection
 
@@ -87,7 +90,7 @@ SELECT = 'select'
 TEXTAREA = 'textarea'
 
 __all__ = ['TEXT', 'NUMBER', 'FLOAT', 'PASSWORD', 'URL', 'DATE', 'TIME', 'input', 'textarea', 'select',
-           'checkbox', 'radio', 'actions', 'file_upload', 'input_group', 'input_update']
+           'checkbox', 'radio', 'actions', 'file_upload', 'slider', 'input_group', 'input_update']
 
 
 def _parse_args(kwargs, excludes=()):
@@ -577,6 +580,28 @@ def file_upload(label='', accept=None, name=None, placeholder='Choose file', mul
     return single_input(item_spec, valid_func, read_file, onchange_func)
 
 
+def slider(label='', *, name=None, value=0, min_value=0, max_value=100, step=1, validate=None, onchange=None,
+           required=None, help_text=None, **other_html_attrs):
+    r"""Range input.
+
+    :param int/float value: The initial value of the slider.
+    :param int/float min_value: The minimum permitted value.
+    :param int/float max_value: The maximum permitted value.
+    :param int step: The stepping interval.
+       Only available when ```value``, ``min_value`` and ``max_value`` are all integer.
+    :param - label, name, validate, onchange, required, help_text, other_html_attrs: Those arguments have the same meaning as for `input()`
+    :return int/float: If one of ``value``, ``min_value`` and ``max_value`` is float,
+       the return value is a float, otherwise an int is returned.
+    """
+    item_spec, valid_func, onchange_func = _parse_args(locals())
+    item_spec['type'] = 'slider'
+    item_spec['float'] = any(isinstance(i, float) for i in (value, min_value, max_value))
+    if item_spec['float']:
+        item_spec['step'] = 'any'
+
+    return single_input(item_spec, valid_func, lambda d: d, onchange_func)
+
+
 def input_group(label='', inputs=None, validate=None, cancelable=False):
     r"""Input group. Request a set of inputs from the user at once.
 

+ 11 - 2
pywebio/pin.py

@@ -83,6 +83,7 @@ The following is the difference between the two in parameters:
 .. autofunction:: put_select
 .. autofunction:: put_checkbox
 .. autofunction:: put_radio
+.. autofunction:: put_slider
 
 Pin utils
 ------------------
@@ -125,7 +126,7 @@ from .session import next_client_event, chose_impl
 
 _html_value_chars = set(string.ascii_letters + string.digits + '_')
 
-__all__ = ['put_input', 'put_textarea', 'put_select', 'put_checkbox', 'put_radio', 'pin', 'pin_update',
+__all__ = ['put_input', 'put_textarea', 'put_select', 'put_checkbox', 'put_radio', 'put_slider', 'pin', 'pin_update',
            'pin_wait_change']
 
 
@@ -186,6 +187,15 @@ def put_radio(name, options=None, *, label='', inline=None, value=None, help_tex
     return _pin_output(single_input_return, scope, position)
 
 
+def put_slider(name, *, label='', value=0, min_value=0, max_value=100, step=1, required=None, help_text=None,
+               scope=Scope.Current, position=OutputPosition.BOTTOM) -> Output:
+    """Output a slide widget. Refer to: `pywebio.input.slide()`"""
+    check_name(name)
+    single_input_return = slider(name=name, label=label, value=value, min_value=min_value, max_value=max_value,
+                                 step=step, required=required, help_text=help_text)
+    return _pin_output(single_input_return, scope, position)
+
+
 @chose_impl
 def get_client_val():
     res = yield next_client_event()
@@ -250,7 +260,6 @@ def pin_update(name, **spec):
     :param str name: The ``name`` of the target input widget.
     :param spec: The pin widget parameters need to be updated.
        Note that those parameters can not be updated: ``type``, ``name``, ``code``, ``multiple``
-
     """
     check_name(name)
     attributes = parse_input_update_spec(spec)

+ 2 - 1
webiojs/src/models/input/index.ts

@@ -4,10 +4,11 @@ import {CheckboxRadio} from "./checkbox_radio"
 import {Textarea} from "./textarea"
 import {File} from "./file"
 import {Select} from "./select"
+import {Slider} from "./slider"
 import {InputItem} from "./base";
 
 
-export const all_input_items = [Input, Actions, CheckboxRadio, Textarea, File, Select];
+export const all_input_items = [Input, Actions, CheckboxRadio, Textarea, File, Select, Slider];
 
 export function get_input_item_from_type(type: string) {
     return type2item[type];

+ 70 - 0
webiojs/src/models/input/slider.ts

@@ -0,0 +1,70 @@
+import {InputItem} from "./base";
+import {deep_copy, make_set} from "../../utils";
+
+const slider_input_tpl = `
+<div class="form-group">
+    {{#label}}<label for="{{id_name}}">{{label}}</label>{{/label}}
+    <input type="range" class="form-control-range" name="{{name}}" min="{{min_value}}" max="{{max_value}}" value="{{value}}" id="{{id_name}}">
+    <div class="invalid-feedback">{{invalid_feedback}}</div>
+    <div class="valid-feedback">{{valid_feedback}}</div>
+    <small id="{{id_name}}_help" class="form-text text-muted">{{help_text}}</small>
+</div>`;
+
+export class Slider extends InputItem {
+    static accept_input_types: string[] = ["slider"];
+
+    files: Blob[] = []; // Files to be uploaded
+    valid = true;
+
+    constructor(spec: any, task_id: string, on_input_event: (event_name: string, input_item: InputItem) => void) {
+        super(spec, task_id, on_input_event);
+    }
+
+    create_element(): JQuery {
+        let spec = deep_copy(this.spec);
+        spec['id_name'] = spec.name + '-' + Math.floor(Math.random() * Math.floor(9999));
+
+        const html = Mustache.render(slider_input_tpl, spec);
+        this.element = $(html);
+        let input_elem = this.element.find('input[type="range"]');
+
+        const ignore_keys = make_set(['value', 'type', 'label', 'invalid_feedback', 'valid_feedback',
+            'help_text', 'min_value', 'max_value', 'id_name', 'name', 'float']);
+        for (let key in this.spec) {
+            if (key in ignore_keys) continue;
+            input_elem.attr(key, this.spec[key]);
+        }
+
+        if (spec.onblur) {
+            // blur事件时,发送当前值到服务器
+            input_elem.on("blur", (e) => {
+                this.on_input_event("blur", this);
+            });
+        }
+        if (spec.onchange) {
+            input_elem.on("change", (e) => {
+                this.on_input_event("change", this);
+            });
+        }
+
+        return this.element;
+    }
+
+    update_input(spec: any): any {
+        let attributes = spec.attributes;
+        this.update_input_helper(-1, attributes);
+    }
+
+    get_value(): any {
+        let val = this.element.find('input[type="range"]').val();
+        if (this.spec['float'])
+            val = parseFloat(val as string);
+        else
+            val = parseInt(val as string);
+        return val;
+    }
+
+}
+
+
+