瀏覽代碼

Merge remote-tracking branch 'origin/validation'

Rodja Trappe 2 年之前
父節點
當前提交
82c4cc3d66
共有 4 個文件被更改,包括 53 次插入5 次删除
  1. 18 2
      nicegui/elements/input.py
  2. 19 2
      nicegui/elements/number.py
  3. 14 0
      tests/test_input.py
  4. 2 1
      website/reference.py

+ 18 - 2
nicegui/elements/input.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Dict, Optional
 
 from .icon import Icon
 from .mixins.value_element import ValueElement
@@ -13,7 +13,8 @@ class Input(ValueElement):
                  value: str = '',
                  password: bool = False,
                  password_toggle_button: bool = False,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable] = None,
+                 validation: Dict[str, Callable] = {}) -> None:
         """Text Input
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.
@@ -22,12 +23,16 @@ class Input(ValueElement):
         If you want to wait until the user confirms the input, you can register a custom event callback, e.g.
         `ui.input(...).on('keydown.enter', ...)` or `ui.input(...).on('blur', ...)`.
 
+        You can use the `validation` parameter to define a dictionary of validation rules.
+        The key of the first rule that fails will be displayed as an error message.
+
         :param label: displayed label for the text input
         :param placeholder: text to show if no value is entered
         :param value: the current value of the text input
         :param password: whether to hide the input (default: False)
         :param password_toggle_button: whether to show a button to toggle the password visibility (default: False)
         :param on_change: callback to execute when the input is confirmed by leaving the focus
+        :param validation: dictionary of validation rules, e.g. ``{'Too short!': lambda value: len(value) < 3}``
         """
         super().__init__(tag='q-input', value=value, on_value_change=on_change)
         if label is not None:
@@ -43,3 +48,14 @@ class Input(ValueElement):
                     icon.props(f'name={"visibility" if is_hidden else "visibility_off"}')
                     self.props(f'type={"text" if is_hidden else "password"}')
                 icon = Icon('visibility_off').classes('cursor-pointer').on('click', toggle_type)
+
+        self.validation = validation
+
+    def on_value_change(self, value: Any) -> None:
+        super().on_value_change(value)
+        for message, check in self.validation.items():
+            if not check(value):
+                self.props(f'error error-message="{message}"')
+                break
+        else:
+            self.props(remove='error')

+ 19 - 2
nicegui/elements/number.py

@@ -11,14 +11,21 @@ class Number(ValueElement):
                  placeholder: Optional[str] = None,
                  value: Optional[float] = None,
                  format: Optional[str] = None,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable] = None,
+                 validation: Dict[str, Callable] = {}) -> None:
         """Number Input
 
+        This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.
+
+        You can use the `validation` parameter to define a dictionary of validation rules.
+        The key of the first rule that fails will be displayed as an error message.
+
         :param label: displayed name for the number input
         :param placeholder: text to show if no value is entered
         :param value: the initial value of the field
-        :param format: a string like '%.2f' to format the displayed value
+        :param format: a string like "%.2f" to format the displayed value
         :param on_change: callback to execute when the input is confirmed by leaving the focus
+        :param validation: dictionary of validation rules, e.g. ``{'Too small!': lambda value: value < 3}``
         """
         self.format = format
         super().__init__(tag='q-input', value=value, on_value_change=on_change)
@@ -27,6 +34,16 @@ class Number(ValueElement):
             self._props['label'] = label
         if placeholder is not None:
             self._props['placeholder'] = placeholder
+        self.validation = validation
+
+    def on_value_change(self, value: Any) -> None:
+        super().on_value_change(value)
+        for message, check in self.validation.items():
+            if not check(value):
+                self.props(f'error error-message="{message}"')
+                break
+        else:
+            self.props(remove='error')
 
     def _msg_to_value(self, msg: Dict) -> Any:
         return float(msg['args']) if msg['args'] else None

+ 14 - 0
tests/test_input.py

@@ -51,3 +51,17 @@ def test_toggle_button(screen: Screen):
     screen.click('visibility')
     screen.wait(0.5)
     assert element.get_attribute('type') == 'password'
+
+
+def test_input_validation(screen: Screen):
+    ui.input('Name', validation={'Too short': lambda value: len(value) >= 5})
+
+    screen.open('/')
+    screen.should_contain('Name')
+
+    element = screen.selenium.find_element(By.XPATH, '//*[@aria-label="Name"]')
+    element.send_keys('John')
+    screen.should_contain('Too short')
+
+    element.send_keys(' Doe')
+    screen.should_not_contain('Too short')

+ 2 - 1
website/reference.py

@@ -130,7 +130,8 @@ def create_full(menu: ui.element) -> None:
     @example(ui.input, menu)
     def input_example():
         ui.input(label='Text', placeholder='start typing',
-                 on_change=lambda e: result.set_text('you typed: ' + e.value))
+                 on_change=lambda e: result.set_text('you typed: ' + e.value),
+                 validation={'Input too long': lambda value: len(value) < 20})
         result = ui.label()
 
     @example(ui.number, menu)