Bläddra i källkod

Introduce element.html_id property (#4633)

Addresses "Introduce an `element.html_id` property which returns the
`id` attribute of the DOM element" in NiceGUI Wishlist.


https://github.com/orgs/zauberzeug/projects/6/views/1?pane=issue&itemId=82941211

Test script: 

```py
from nicegui import ui

my_label = ui.label('Hello world!')
ui.label(f"ID of my_label: {my_label.id}")
ui.label(f"HTML ID of my_label: {my_label.html_id}")
ui.label(f"HTML ID of my_label (inferred from ID): {'c'+str(my_label.id)}")

ui.run(show=False)
```

Test result: 

<img width="230" alt="{5C9DECA0-8482-4610-B44C-611351EC4A30}"
src="https://github.com/user-attachments/assets/11a2a2ec-a6ff-4527-a7d6-7a1196161b5c"
/>

<img width="640" alt="{D688002B-A8F0-443D-B04A-3287AA1D714E}"
src="https://github.com/user-attachments/assets/404f4ce0-043c-4b1c-afc1-5f883f785cbe"
/>

> The "Hello world!" indeed has id "c4"

---

I'll be honest, not sure what's the use of this wishlist entry. Why
bother, when `f'c{element.id}'` is the underlying logic, and it's
trivial enough to just do that in your code?
Evan Chan 3 veckor sedan
förälder
incheckning
c5433b8ff8

+ 5 - 0
nicegui/element.py

@@ -531,3 +531,8 @@ class Element(Visibility):
                 result += f'\n {line}'
 
         return result
+
+    @property
+    def html_id(self) -> str:
+        """The ID of the element in the HTML DOM."""
+        return f'c{self.id}'

+ 1 - 1
nicegui/elements/link.py

@@ -27,7 +27,7 @@ class Link(TextElement, component='link.js', default_classes='nicegui-link'):
         if isinstance(target, str):
             self._props['href'] = target
         elif isinstance(target, Element):
-            self._props['href'] = f'#c{target.id}'
+            self._props['href'] = f'#{target.html_id}'
         elif callable(target):
             self._props['href'] = Client.page_routes[target]
         self._props['target'] = '_blank' if new_tab else '_self'

+ 1 - 1
nicegui/elements/teleport.py

@@ -14,6 +14,6 @@ class Teleport(Element, component='teleport.js'):
         """
         super().__init__()
         if isinstance(to, Element):
-            to = f'#c{to.id}'
+            to = f'#{to.html_id}'
         self._props['to'] = to
         self._update_method = 'update'

+ 1 - 1
nicegui/functions/navigate.py

@@ -61,7 +61,7 @@ class Navigate:
         if isinstance(target, str):
             path = target
         elif isinstance(target, Element):
-            path = f'#c{target.id}'
+            path = f'#{target.html_id}'
         elif callable(target):
             path = Client.page_routes[target]
         else:

+ 1 - 1
nicegui/testing/screen.py

@@ -205,7 +205,7 @@ class Screen:
 
     def find_element(self, element: ui.element) -> WebElement:
         """Find the given NiceGUI element."""
-        return self.selenium.find_element(By.ID, f'c{element.id}')
+        return self.selenium.find_element(By.ID, element.html_id)
 
     def find_by_class(self, name: str) -> WebElement:
         """Find the element with the given CSS class."""

+ 12 - 11
tests/test_scene.py

@@ -18,7 +18,8 @@ def test_moving_sphere_with_timer(screen: Screen):
     def position() -> float:
         for _ in range(3):
             try:
-                pos = screen.selenium.execute_script(f'return scene_c{scene.id}.getObjectByName("sphere").position.z')
+                pos = screen.selenium.execute_script(
+                    f'return scene_{scene.html_id}.getObjectByName("sphere").position.z')
                 if pos is not None:
                     return pos
             except JavascriptException as e:
@@ -41,18 +42,18 @@ def test_no_object_duplication_on_index_client(screen: Screen):
     screen.open('/')
     screen.switch_to(0)
     screen.wait(0.2)
-    assert screen.selenium.execute_script(f'return scene_c{scene.id}.children.length') == 5
+    assert screen.selenium.execute_script(f'return scene_{scene.html_id}.children.length') == 5
 
 
 def test_no_object_duplication_with_page_builder(screen: Screen):
-    scene_ids: List[int] = []
+    scene_html_ids: List[int] = []
 
     @ui.page('/')
     def page():
         with ui.scene() as scene:
             sphere = scene.sphere().move(0, -4, 0)
             ui.timer(0.1, lambda: sphere.move(0, sphere.y + 0.5, 0))
-        scene_ids.append(scene.id)
+        scene_html_ids.append(scene.html_id)
 
     screen.open('/')
     screen.wait(0.4)
@@ -60,10 +61,10 @@ def test_no_object_duplication_with_page_builder(screen: Screen):
     screen.open('/')
     screen.switch_to(0)
     screen.wait(0.2)
-    assert screen.selenium.execute_script(f'return scene_c{scene_ids[0]}.children.length') == 5
+    assert screen.selenium.execute_script(f'return scene_{scene_html_ids[0]}.children.length') == 5
     screen.switch_to(1)
     screen.wait(0.2)
-    assert screen.selenium.execute_script(f'return scene_c{scene_ids[1]}.children.length') == 5
+    assert screen.selenium.execute_script(f'return scene_{scene_html_ids[1]}.children.length') == 5
 
 
 def test_deleting_group(screen: Screen):
@@ -95,10 +96,10 @@ def test_replace_scene(screen: Screen):
 
     screen.open('/')
     screen.wait(0.5)
-    assert screen.selenium.execute_script(f'return scene_c{scene.id}.children[4].name') == 'sphere'
+    assert screen.selenium.execute_script(f'return scene_{scene.html_id}.children[4].name') == 'sphere'
     screen.click('Replace scene')
     screen.wait(0.5)
-    assert screen.selenium.execute_script(f'return scene_c{scene.id}.children[4].name') == 'box'
+    assert screen.selenium.execute_script(f'return scene_{scene.html_id}.children[4].name') == 'box'
 
 
 def test_create_dynamically(screen: Screen):
@@ -124,7 +125,7 @@ def test_object_creation_via_context(screen: Screen):
 
     screen.open('/')
     screen.wait(0.5)
-    assert screen.selenium.execute_script(f'return scene_c{scene.id}.children[4].name') == 'box'
+    assert screen.selenium.execute_script(f'return scene_{scene.html_id}.children[4].name') == 'box'
 
 
 def test_object_creation_via_attribute(screen: Screen):
@@ -133,7 +134,7 @@ def test_object_creation_via_attribute(screen: Screen):
 
     screen.open('/')
     screen.wait(0.5)
-    assert screen.selenium.execute_script(f'return scene_c{scene.id}.children[4].name') == 'box'
+    assert screen.selenium.execute_script(f'return scene_{scene.html_id}.children[4].name') == 'box'
 
 
 def test_clearing_scene(screen: Screen):
@@ -157,4 +158,4 @@ def test_gltf(screen: Screen):
 
     screen.open('/')
     screen.wait(1.0)
-    assert screen.selenium.execute_script(f'return scene_c{scene.id}.children.length') == 5
+    assert screen.selenium.execute_script(f'return scene_{scene.html_id}.children.length') == 5

+ 3 - 3
website/documentation/content/radio_documentation.py

@@ -15,11 +15,11 @@ def main_demo() -> None:
 def arbitrary_content():
     options = ['Star', 'Thump Up', 'Heart']
     radio = ui.radio({x: '' for x in options}, value='Star').props('inline')
-    with ui.teleport(f'#c{radio.id} > div:nth-child(1) .q-radio__label'):
+    with ui.teleport(f'#{radio.html_id} > div:nth-child(1) .q-radio__label'):
         ui.icon('star', size='md')
-    with ui.teleport(f'#c{radio.id} > div:nth-child(2) .q-radio__label'):
+    with ui.teleport(f'#{radio.html_id} > div:nth-child(2) .q-radio__label'):
         ui.icon('thumb_up', size='md')
-    with ui.teleport(f'#c{radio.id} > div:nth-child(3) .q-radio__label'):
+    with ui.teleport(f'#{radio.html_id} > div:nth-child(3) .q-radio__label'):
         ui.icon('favorite', size='md')
     ui.label().bind_text_from(radio, 'value')
 

+ 5 - 5
website/documentation/content/teleport_documentation.py

@@ -8,7 +8,7 @@ def main_demo() -> None:
     markdown = ui.markdown('Enter your **name**!')
 
     def inject_input():
-        with ui.teleport(f'#c{markdown.id} strong'):
+        with ui.teleport(f'#{markdown.html_id} strong'):
             ui.input('name').classes('inline-flex').props('dense outlined')
 
     ui.button('inject input', on_click=inject_input)
@@ -20,11 +20,11 @@ def main_demo() -> None:
 def arbitrary_content():
     options = ['Star', 'Thump Up', 'Heart']
     radio = ui.radio({x: '' for x in options}, value='Star').props('inline')
-    with ui.teleport(f'#c{radio.id} > div:nth-child(1) .q-radio__label'):
+    with ui.teleport(f'#{radio.html_id} > div:nth-child(1) .q-radio__label'):
         ui.icon('star', size='md')
-    with ui.teleport(f'#c{radio.id} > div:nth-child(2) .q-radio__label'):
+    with ui.teleport(f'#{radio.html_id} > div:nth-child(2) .q-radio__label'):
         ui.icon('thumb_up', size='md')
-    with ui.teleport(f'#c{radio.id} > div:nth-child(3) .q-radio__label'):
+    with ui.teleport(f'#{radio.html_id} > div:nth-child(3) .q-radio__label'):
         ui.icon('favorite', size='md')
     ui.label().bind_text_from(radio, 'value')
 
@@ -44,7 +44,7 @@ def graph_in_table():
     ]
     table = ui.table(columns=columns, rows=rows, row_key='name').classes('w-72')
     for r, row in enumerate(rows):
-        with ui.teleport(f'#c{table.id} tr:nth-child({r+1}) td:nth-child(2)'):
+        with ui.teleport(f'#{table.html_id} tr:nth-child({r+1}) td:nth-child(2)'):
             ui.echart({
                 'xAxis': {'type': 'category', 'show': False},
                 'yAxis': {'type': 'value', 'show': False},