Browse Source

binding: cleaned up binding framework for number inputs (other elements still to do)

Falko Schindler 4 years ago
parent
commit
d803e675a2
5 changed files with 87 additions and 38 deletions
  1. 5 0
      main.py
  2. 38 0
      nicegui/binding.py
  3. 7 32
      nicegui/elements/element.py
  4. 34 5
      nicegui/elements/number.py
  5. 3 1
      nicegui/nicegui.py

+ 5 - 0
main.py

@@ -4,6 +4,11 @@ from datetime import datetime
 from matplotlib import pyplot as plt
 import numpy as np
 
+with ui.row(), ui.card():
+    ui.label('Binding', 'h5')
+    n1 = ui.number(value=0.5, decimals=2)
+    n2 = ui.number(decimals=3).bind('value', n1, 'value')
+
 with ui.row():
     with ui.card():
         ui.label('Interactive elements', 'h5')

+ 38 - 0
nicegui/binding.py

@@ -0,0 +1,38 @@
+from typing import Any
+import asyncio
+
+class Binding:
+
+    all_bindings = []
+
+    def __init__(self, element, element_attribute: str, model: Any, model_attribute: str) -> None:
+
+        self.element = element
+        self.element_attribute = element_attribute
+        self.model = model
+        self.model_attribute = model_attribute
+
+    async def update_element(self):
+
+        model_value = getattr(self.model, self.model_attribute)
+        element_value = getattr(self.element, self.element_attribute)
+        if element_value != model_value:
+            setattr(self.element, self.element_attribute, model_value)
+            await self.element.parent_view.update()
+
+    def update_model(self):
+
+        model_value = getattr(self.model, self.model_attribute)
+        element_value = getattr(self.element, self.element_attribute)
+        if model_value != element_value:
+            setattr(self.model, self.model_attribute, element_value)
+
+    @staticmethod
+    async def loop():
+
+        while True:
+
+            for binding in Binding.all_bindings:
+                await binding.update_element()
+
+            await asyncio.sleep(0.1)

+ 7 - 32
nicegui/elements/element.py

@@ -1,12 +1,11 @@
 import justpy as jp
-import asyncio
-from ..utils import handle_exceptions, provide_arguments
+from ..binding import Binding
 
 class Element:
 
     wp: None
     view_stack = []
-    bindings = []
+    all_bindings = []
 
     def __init__(self, view: jp.HTMLBaseComponent):
 
@@ -14,6 +13,7 @@ class Element:
         self.parent_view.add(view)
         view.add_page(self.wp)
         self.view = view
+        self.bindings = []
 
     def set_classes(self, classes: str):
 
@@ -35,33 +35,8 @@ class Element:
         self.view.style += ' ' + style
         return self
 
-    def bind(self, view_attribute, model, model_attribute, interval, transform):
+    def bind(self, attribute, model, model_attribute):
 
-        async def loop():
-
-            while True:
-                model_value = transform(getattr(model, model_attribute))
-                if getattr(self.view, view_attribute) != model_value:
-                    print("Update view", view_attribute, 'to', model_value,
-                          '/ was:', getattr(self.view, view_attribute))
-                    setattr(self.view, view_attribute, model_value)
-                    await self.parent_view.update()
-                await asyncio.sleep(interval)
-
-        self.bindings.append(loop())
-
-    def bind_text(self, model, attribute, interval=0.1, transform=lambda x: x):
-
-        self.bind('text', model, attribute, interval, transform)
-
-    def bind_value(self, model, attribute, interval=0.1, transform=lambda x: x):
-
-        self.bind('value', model, attribute, interval, transform)
-
-        def update_model(_, event):
-            model_value = transform(getattr(model, attribute))
-            if model_value != transform(float(event.value)):
-                print("Update model", attribute, 'to', event.value, '/ was:', model_value)
-                setattr(model, attribute, event.value)
-
-        self.view.on('change', handle_exceptions(update_model))
+        binding = Binding(self, attribute, model, model_attribute)
+        self.bindings.append(binding)
+        Binding.all_bindings.append(binding)

+ 34 - 5
nicegui/elements/number.py

@@ -1,7 +1,8 @@
+from nicegui.binding import Binding
 import justpy as jp
 from typing import Callable
 from .element import Element
-from ..utils import handle_exceptions, provide_arguments
+from ..utils import handle_exceptions
 
 class Number(Element):
 
@@ -14,15 +15,43 @@ class Number(Element):
                  design: str = '',
                  on_change: Callable = None):
 
+        self.decimals = decimals
+        self.on_change = on_change
+
         view = jp.QInput(
             type='number',
             label=label,
             placeholder=placeholder,
-            value=value if decimals is None else round(value, decimals),
             **{key: True for key in design.split()},
         )
-
-        if on_change is not None:
-            view.on('change', handle_exceptions(provide_arguments(on_change, 'value')))
+        view.on('change', handle_exceptions(self.handle_change))
 
         super().__init__(view)
+
+        self.value = value
+
+    @property
+    def value(self):
+
+        return self.value_
+
+    @value.setter
+    def value(self, value: float):
+
+        self.value_ = value
+        if value is None:
+            self.view.value = None
+        elif self.decimals is None:
+            self.view.value = str(value)
+        else:
+            self.view.value = f'{value:.{self.decimals}f}'
+
+    def handle_change(self, sender, msg):
+
+        self.value = float(msg['value'])
+
+        if self.on_change is not None:
+            self.on_change(sender, self.value)
+
+        for binding in self.bindings:
+            binding.update_model()

+ 3 - 1
nicegui/nicegui.py

@@ -7,6 +7,7 @@ import webbrowser
 from .ui import Ui
 from .timer import Timer
 from .elements.element import Element
+from .binding import Binding
 
 # start uvicorn with auto-reload; afterwards the auto-reloaded process should not start uvicorn again
 if not inspect.stack()[-2].filename.endswith('spawn.py'):
@@ -23,7 +24,8 @@ jp.justpy(lambda: wp, start_server=False)
 
 @jp.app.on_event('startup')
 def startup():
-    [jp.run_task(t) for t in Timer.tasks + Element.bindings]
+    [jp.run_task(t) for t in Timer.tasks]
+    jp.run_task(Binding.loop())
 
 Element.wp = wp
 Element.view_stack = [main]