Browse Source

Merge branch 'main' of github.com:zauberzeug/nicegui

Rodja Trappe 2 years ago
parent
commit
0f675212c6
1 changed files with 197 additions and 157 deletions
  1. 197 157
      api_docs_and_examples.py

+ 197 - 157
api_docs_and_examples.py

@@ -1,6 +1,5 @@
 import inspect
 import inspect
 import re
 import re
-from contextlib import contextmanager
 from typing import Callable, Union
 from typing import Callable, Union
 
 
 import docutils.core
 import docutils.core
@@ -14,57 +13,35 @@ REGEX_H4 = re.compile(r'<h4.*?>(.*?)</h4>')
 SPECIAL_CHARACTERS = re.compile('[^(a-z)(A-Z)(0-9)-]')
 SPECIAL_CHARACTERS = re.compile('[^(a-z)(A-Z)(0-9)-]')
 
 
 
 
-@contextmanager
-def example(content: Union[Callable, type, str], tight: bool = False) -> None:
-    callFrame = inspect.currentframe().f_back.f_back
-    begin = callFrame.f_lineno
+class example:
 
 
-    def add_html_anchor(element: ui.html):
-        html = element.content
-        match = REGEX_H4.search(html)
-        if not match:
-            return
-        headline = match.groups()[0].strip()
-        headline_id = SPECIAL_CHARACTERS.sub('_', headline).lower()
-        if not headline_id:
-            return
+    def __init__(self, content: Union[Callable, type, str], tight: bool = False) -> None:
+        self.content = content
+        self.markdown_classes = f'mr-8 w-full flex-none lg:w-{48 if tight else 80} xl:w-80'
+        self.rendering_classes = f'w-{48 if tight else 64} flex-none lg:mt-12'
+        self.source_classes = f'w-80 flex-grow overflow-auto lg:mt-12'
 
 
-        icon = '<span class="material-icons">link</span>'
-        anchor = f'<a href="reference#{headline_id}" class="text-gray-300 hover:text-black">{icon}</a>'
-        html = html.replace('<h4', f'<h4 id="{headline_id}"', 1)
-        html = html.replace('</h4>', f' {anchor}</h4>', 1)
-        element.view.inner_html = html
-
-    with ui.row().classes('flex w-full'):
-        markdown_classes = f'mr-8 w-full flex-none lg:w-{48 if tight else 80} xl:w-80'
-        rendering_classes = f'w-{48 if tight else 64} flex-none lg:mt-12'
-        source_classes = f'w-80 flex-grow overflow-auto lg:mt-12'
-
-        if isinstance(content, str):
-            add_html_anchor(ui.markdown(content).classes(markdown_classes))
-        else:
-            doc = content.__doc__ or content.__init__.__doc__
-            html = docutils.core.publish_parts(doc, writer_name='html')['html_body']
-            html = html.replace('<p>', '<h4>', 1)
-            html = html.replace('</p>', '</h4>', 1)
-            html = apply_tailwind(html)
-            add_html_anchor(ui.html(html).classes(markdown_classes))
-
-        try:
-            with ui.card().classes(rendering_classes):
-                yield
-        finally:
-            code: str = open(__file__).read()
-            end = begin + 1
-            lines = code.splitlines()
-            while True:
-                end += 1
-                if end >= len(lines):
-                    break
-                if inspect.indentsize(lines[end]) < inspect.indentsize(lines[begin]) and lines[end]:
-                    break
-            code = lines[begin:end]
+    def __call__(self, f: Callable) -> Callable:
+        with ui.row().classes('flex w-full'):
+            if isinstance(self.content, str):
+                self._add_html_anchor(ui.markdown(self.content).classes(self.markdown_classes))
+            else:
+                doc = self.content.__doc__ or self.content.__init__.__doc__
+                html: str = docutils.core.publish_parts(doc, writer_name='html')['html_body']
+                html = html.replace('<p>', '<h4>', 1)
+                html = html.replace('</p>', '</h4>', 1)
+                html = apply_tailwind(html)
+                self._add_html_anchor(ui.html(html).classes(self.markdown_classes))
+
+            with ui.card().classes(self.rendering_classes):
+                f()
+
+            code = inspect.getsource(f).splitlines()
+            while not code[0].startswith(' ' * 8):
+                del code[0]
             code = [l[8:] for l in code]
             code = [l[8:] for l in code]
+            while code[0].startswith('global '):
+                del code[0]
             code.insert(0, '```python')
             code.insert(0, '```python')
             code.insert(1, 'from nicegui import ui')
             code.insert(1, 'from nicegui import ui')
             if code[2].split()[0] not in ['from', 'import']:
             if code[2].split()[0] not in ['from', 'import']:
@@ -75,29 +52,47 @@ def example(content: Union[Callable, type, str], tight: bool = False) -> None:
                 if line.startswith('# ui.run('):
                 if line.startswith('# ui.run('):
                     break
                     break
             else:
             else:
+                code.append('')
                 code.append('ui.run()')
                 code.append('ui.run()')
             code.append('```')
             code.append('```')
             code = '\n'.join(code)
             code = '\n'.join(code)
-            ui.markdown(code).classes(source_classes)
+            ui.markdown(code).classes(self.source_classes)
+        return f
+
+    def _add_html_anchor(self, element: ui.html) -> None:
+        html = element.content
+        match = REGEX_H4.search(html)
+        if not match:
+            return
+        headline = match.groups()[0].strip()
+        headline_id = SPECIAL_CHARACTERS.sub('_', headline).lower()
+        if not headline_id:
+            return
+
+        icon = '<span class="material-icons">link</span>'
+        anchor = f'<a href="reference#{headline_id}" class="text-gray-300 hover:text-black">{icon}</a>'
+        html = html.replace('<h4', f'<h4 id="{headline_id}"', 1)
+        html = html.replace('</h4>', f' {anchor}</h4>', 1)
+        element.view.inner_html = html
 
 
 
 
 def create_intro() -> None:
 def create_intro() -> None:
     # add docutils css to webpage
     # add docutils css to webpage
     ui.add_head_html(docutils.core.publish_parts('', writer_name='html')['stylesheet'])
     ui.add_head_html(docutils.core.publish_parts('', writer_name='html')['stylesheet'])
 
 
-    hello_world = '''#### Hello, World!
+    @example('''#### Hello, World!
 
 
 Creating a user interface with NiceGUI is as simple as writing a single line of code.
 Creating a user interface with NiceGUI is as simple as writing a single line of code.
-'''
-    with example(hello_world, tight=True):
+''', tight=True)
+    def hello_world_example():
         ui.label('Hello, world!')
         ui.label('Hello, world!')
         ui.markdown('Have a look at the full <br/> [API reference](reference)!')
         ui.markdown('Have a look at the full <br/> [API reference](reference)!')
 
 
-    common_elements = '''#### Common UI Elements
+    @example('''#### Common UI Elements
 
 
 NiceGUI comes with a collection of commonly used UI elements.
 NiceGUI comes with a collection of commonly used UI elements.
-'''
-    with example(common_elements, tight=True):
+''', tight=True)
+    def common_elements_example():
         ui.button('Button', on_click=lambda: ui.notify('Click'))
         ui.button('Button', on_click=lambda: ui.notify('Click'))
         ui.checkbox('Checkbox', on_change=lambda e: ui.notify('Checked' if e.value else 'Unchecked'))
         ui.checkbox('Checkbox', on_change=lambda e: ui.notify('Checked' if e.value else 'Unchecked'))
         ui.switch('Switch', on_change=lambda e: ui.notify('Switched' if e.value else 'Unswitched'))
         ui.switch('Switch', on_change=lambda e: ui.notify('Switched' if e.value else 'Unswitched'))
@@ -106,16 +101,14 @@ NiceGUI comes with a collection of commonly used UI elements.
         ui.select(['One', 'Two'], value='One', on_change=lambda e: ui.notify(e.value))
         ui.select(['One', 'Two'], value='One', on_change=lambda e: ui.notify(e.value))
         ui.link('And many more...', '/reference').classes('text-lg')
         ui.link('And many more...', '/reference').classes('text-lg')
 
 
-    binding = '''#### Value Binding
+    @example('''#### Value Binding
 
 
 Binding values between UI elements or [to data models](http://127.0.0.1:8080/reference#bindings) is built into NiceGUI.
 Binding values between UI elements or [to data models](http://127.0.0.1:8080/reference#bindings) is built into NiceGUI.
-'''
-    with example(binding, tight=True):
+''', tight=True)
+    def binding_example():
         slider = ui.slider(min=0, max=100, value=50)
         slider = ui.slider(min=0, max=100, value=50)
         ui.number('Value').bind_value(slider, 'value').classes('fit')
         ui.number('Value').bind_value(slider, 'value').classes('fit')
 
 
-    # HACK: this comment prevents another blank line sneaking into the example above
-
 
 
 def create_full() -> None:
 def create_full() -> None:
     # add docutils css to webpage
     # add docutils css to webpage
@@ -128,87 +121,106 @@ def create_full() -> None:
 
 
     h3('Basic Elements')
     h3('Basic Elements')
 
 
-    with example(ui.label):
+    @example(ui.label)
+    def label_example():
         ui.label('some label')
         ui.label('some label')
 
 
-    with example(ui.icon):
+    @example(ui.icon)
+    def icon_example():
         ui.icon('thumb_up')
         ui.icon('thumb_up')
 
 
-    with example(ui.link):
+    @example(ui.link)
+    def link_example():
         ui.link('NiceGUI on GitHub', 'https://github.com/zauberzeug/nicegui')
         ui.link('NiceGUI on GitHub', 'https://github.com/zauberzeug/nicegui')
 
 
-    with example(ui.button):
+    @example(ui.button)
+    def button_example():
         ui.button('Click me!', on_click=lambda: ui.notify(f'You clicked me!'))
         ui.button('Click me!', on_click=lambda: ui.notify(f'You clicked me!'))
 
 
-    with example(ui.badge):
+    @example(ui.badge)
+    def badge_example():
         with ui.button('Click me!', on_click=lambda: badge.set_text(int(badge.text) + 1)):
         with ui.button('Click me!', on_click=lambda: badge.set_text(int(badge.text) + 1)):
             badge = ui.badge('0', color='red').props('floating')
             badge = ui.badge('0', color='red').props('floating')
 
 
-    with example(ui.toggle):
+    @example(ui.toggle)
+    def toggle_example():
         toggle1 = ui.toggle([1, 2, 3], value=1)
         toggle1 = ui.toggle([1, 2, 3], value=1)
         toggle2 = ui.toggle({1: 'A', 2: 'B', 3: 'C'}).bind_value(toggle1, 'value')
         toggle2 = ui.toggle({1: 'A', 2: 'B', 3: 'C'}).bind_value(toggle1, 'value')
 
 
-    with example(ui.radio):
+    @example(ui.radio)
+    def radio_example():
         radio1 = ui.radio([1, 2, 3], value=1).props('inline')
         radio1 = ui.radio([1, 2, 3], value=1).props('inline')
         radio2 = ui.radio({1: 'A', 2: 'B', 3: 'C'}).props('inline').bind_value(radio1, 'value')
         radio2 = ui.radio({1: 'A', 2: 'B', 3: 'C'}).props('inline').bind_value(radio1, 'value')
 
 
-    with example(ui.select):
+    @example(ui.select)
+    def select_example():
         select1 = ui.select([1, 2, 3], value=1)
         select1 = ui.select([1, 2, 3], value=1)
         select2 = ui.select({1: 'One', 2: 'Two', 3: 'Three'}).bind_value(select1, 'value')
         select2 = ui.select({1: 'One', 2: 'Two', 3: 'Three'}).bind_value(select1, 'value')
 
 
-    with example(ui.checkbox):
+    @example(ui.checkbox)
+    def checkbox_example():
         checkbox = ui.checkbox('check me')
         checkbox = ui.checkbox('check me')
         ui.label('Check!').bind_visibility_from(checkbox, 'value')
         ui.label('Check!').bind_visibility_from(checkbox, 'value')
 
 
-    with example(ui.switch):
+    @example(ui.switch)
+    def switch_example():
         switch = ui.switch('switch me')
         switch = ui.switch('switch me')
         ui.label('Switch!').bind_visibility_from(switch, 'value')
         ui.label('Switch!').bind_visibility_from(switch, 'value')
 
 
-    with example(ui.slider):
+    @example(ui.slider)
+    def slider_example():
         slider = ui.slider(min=0, max=100, value=50).props('label')
         slider = ui.slider(min=0, max=100, value=50).props('label')
         ui.label().bind_text_from(slider, 'value')
         ui.label().bind_text_from(slider, 'value')
 
 
-    with example(ui.joystick):
+    @example(ui.joystick)
+    def joystick_example():
         ui.joystick(color='blue', size=50,
         ui.joystick(color='blue', size=50,
                     on_move=lambda msg: coordinates.set_text(f'{msg.data.vector.x:.3f}, {msg.data.vector.y:.3f}'),
                     on_move=lambda msg: coordinates.set_text(f'{msg.data.vector.x:.3f}, {msg.data.vector.y:.3f}'),
                     on_end=lambda msg: coordinates.set_text('0, 0'))
                     on_end=lambda msg: coordinates.set_text('0, 0'))
         coordinates = ui.label('0, 0')
         coordinates = ui.label('0, 0')
 
 
-    with example(ui.input):
+    @example(ui.input)
+    def input_example():
         ui.input(label='Text', placeholder='press ENTER to apply',
         ui.input(label='Text', placeholder='press ENTER to apply',
                  on_change=lambda e: input_result.set_text('you typed: ' + e.value))
                  on_change=lambda e: input_result.set_text('you typed: ' + e.value))
         input_result = ui.label()
         input_result = ui.label()
 
 
-    with example(ui.number):
+    @example(ui.number)
+    def number_example():
         ui.number(label='Number', value=3.1415927, format='%.2f',
         ui.number(label='Number', value=3.1415927, format='%.2f',
                   on_change=lambda e: number_result.set_text(f'you entered: {e.value}'))
                   on_change=lambda e: number_result.set_text(f'you entered: {e.value}'))
         number_result = ui.label()
         number_result = ui.label()
 
 
-    with example(ui.color_input):
+    @example(ui.color_input)
+    def color_input_example():
         color_label = ui.label('Change my color!')
         color_label = ui.label('Change my color!')
         ui.color_input(label='Color', value='#000000',
         ui.color_input(label='Color', value='#000000',
                        on_change=lambda e: color_label.style(f'color:{e.value}'))
                        on_change=lambda e: color_label.style(f'color:{e.value}'))
 
 
-    with example(ui.color_picker):
+    @example(ui.color_picker)
+    def color_picker_example():
         picker = ui.color_picker(on_pick=lambda e: button.style(f'background-color:{e.color}!important'))
         picker = ui.color_picker(on_pick=lambda e: button.style(f'background-color:{e.color}!important'))
         button = ui.button(on_click=picker.open).props('icon=colorize')
         button = ui.button(on_click=picker.open).props('icon=colorize')
 
 
-    with example(ui.upload):
+    @example(ui.upload)
+    def upload_example():
         ui.upload(on_upload=lambda e: ui.notify(f'{len(e.files[0])} bytes'))
         ui.upload(on_upload=lambda e: ui.notify(f'{len(e.files[0])} bytes'))
 
 
     h3('Markdown and HTML')
     h3('Markdown and HTML')
 
 
-    with example(ui.markdown):
+    @example(ui.markdown)
+    def markdown_example():
         ui.markdown('''This is **Markdown**.''')
         ui.markdown('''This is **Markdown**.''')
 
 
-    with example(ui.html):
+    @example(ui.html)
+    def html_example():
         ui.html('This is <strong>HTML</strong>.')
         ui.html('This is <strong>HTML</strong>.')
 
 
-    svg = '''#### SVG
+    @example('''#### SVG
 
 
 You can add Scalable Vector Graphics using the `ui.html` element.
 You can add Scalable Vector Graphics using the `ui.html` element.
-'''
-    with example(svg):
+''')
+    def svg_example():
         content = '''
         content = '''
             <svg viewBox="0 0 200 200" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
             <svg viewBox="0 0 200 200" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
             <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
             <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
@@ -220,17 +232,18 @@ You can add Scalable Vector Graphics using the `ui.html` element.
 
 
     h3('Images')
     h3('Images')
 
 
-    with example(ui.image):
+    @example(ui.image)
+    def image_example():
         ui.image('http://placeimg.com/640/360/tech')
         ui.image('http://placeimg.com/640/360/tech')
 
 
-    captions_and_overlays = '''#### Captions and Overlays
+    @example('''#### Captions and Overlays
 
 
 By nesting elements inside a `ui.image` you can create augmentations.
 By nesting elements inside a `ui.image` you can create augmentations.
 
 
 Use [Quasar classes](https://quasar.dev/vue-components/img) for positioning and styling captions.
 Use [Quasar classes](https://quasar.dev/vue-components/img) for positioning and styling captions.
 To overlay an SVG, make the `viewBox` exactly the size of the image and provide `100%` width/height to match the actual rendered size.
 To overlay an SVG, make the `viewBox` exactly the size of the image and provide `100%` width/height to match the actual rendered size.
-'''
-    with example(captions_and_overlays):
+''')
+    def captions_and_overlays_example():
         with ui.image('http://placeimg.com/640/360/nature'):
         with ui.image('http://placeimg.com/640/360/nature'):
             ui.label('Nice!').classes('absolute-bottom text-subtitle2 text-center')
             ui.label('Nice!').classes('absolute-bottom text-subtitle2 text-center')
 
 
@@ -241,7 +254,8 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
                 </svg>'''
                 </svg>'''
             ui.html(content).style('background:transparent')
             ui.html(content).style('background:transparent')
 
 
-    with example(ui.interactive_image):
+    @example(ui.interactive_image)
+    def interactive_image_example():
         from nicegui.events import MouseEventArguments
         from nicegui.events import MouseEventArguments
 
 
         def mouse_handler(e: MouseEventArguments):
         def mouse_handler(e: MouseEventArguments):
@@ -254,7 +268,8 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
 
 
     h3('Data Elements')
     h3('Data Elements')
 
 
-    with example(ui.table):
+    @example(ui.table)
+    def table_example():
         table = ui.table({
         table = ui.table({
             'columnDefs': [
             'columnDefs': [
                 {'headerName': 'Name', 'field': 'name'},
                 {'headerName': 'Name', 'field': 'name'},
@@ -273,7 +288,8 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
 
 
         ui.button('Update', on_click=update)
         ui.button('Update', on_click=update)
 
 
-    with example(ui.chart):
+    @example(ui.chart)
+    def chart_example():
         from numpy.random import random
         from numpy.random import random
 
 
         chart = ui.chart({
         chart = ui.chart({
@@ -292,7 +308,8 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
 
 
         ui.button('Update', on_click=update)
         ui.button('Update', on_click=update)
 
 
-    with example(ui.plot):
+    @example(ui.plot)
+    def plot_example():
         import numpy as np
         import numpy as np
         from matplotlib import pyplot as plt
         from matplotlib import pyplot as plt
 
 
@@ -303,7 +320,9 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
             plt.xlabel('time (s)')
             plt.xlabel('time (s)')
             plt.ylabel('Damped oscillation')
             plt.ylabel('Damped oscillation')
 
 
-    with example(ui.line_plot):
+    @example(ui.line_plot)
+    def line_plot_example():
+        global line_checkbox
         from datetime import datetime
         from datetime import datetime
 
 
         import numpy as np
         import numpy as np
@@ -321,15 +340,18 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
         line_updates = ui.timer(0.1, update_line_plot, active=False)
         line_updates = ui.timer(0.1, update_line_plot, active=False)
         line_checkbox = ui.checkbox('active').bind_value(line_updates, 'active')
         line_checkbox = ui.checkbox('active').bind_value(line_updates, 'active')
 
 
-    with example(ui.linear_progress):
+    @example(ui.linear_progress)
+    def linear_progress_example():
         slider = ui.slider(min=0, max=1, step=0.01, value=0.5)
         slider = ui.slider(min=0, max=1, step=0.01, value=0.5)
         ui.linear_progress().bind_value_from(slider, 'value')
         ui.linear_progress().bind_value_from(slider, 'value')
 
 
-    with example(ui.circular_progress):
+    @example(ui.circular_progress)
+    def circular_progress_example():
         slider = ui.slider(min=0, max=1, step=0.01, value=0.5)
         slider = ui.slider(min=0, max=1, step=0.01, value=0.5)
         ui.circular_progress().bind_value_from(slider, 'value')
         ui.circular_progress().bind_value_from(slider, 'value')
 
 
-    with example(ui.scene):
+    @example(ui.scene)
+    def scene_example():
         with ui.scene(width=225, height=225) as scene:
         with ui.scene(width=225, height=225) as scene:
             scene.sphere().material('#4488ff')
             scene.sphere().material('#4488ff')
             scene.cylinder(1, 0.5, 2, 20).material('#ff8800', opacity=0.5).move(-2, 1)
             scene.cylinder(1, 0.5, 2, 20).material('#ff8800', opacity=0.5).move(-2, 1)
@@ -353,13 +375,15 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
             scene.text('2D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(z=2)
             scene.text('2D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(z=2)
             scene.text3d('3D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(y=-2).scale(.05)
             scene.text3d('3D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(y=-2).scale(.05)
 
 
-    with example(ui.tree):
+    @example(ui.tree)
+    def tree_example():
         ui.tree([
         ui.tree([
             {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
             {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
             {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
             {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
         ], label_key='id', on_select=lambda e: ui.notify(e.value))
         ], label_key='id', on_select=lambda e: ui.notify(e.value))
 
 
-    with example(ui.log):
+    @example(ui.log)
+    def log_example():
         from datetime import datetime
         from datetime import datetime
 
 
         log = ui.log(max_lines=10).classes('w-full h-16')
         log = ui.log(max_lines=10).classes('w-full h-16')
@@ -367,31 +391,34 @@ To overlay an SVG, make the `viewBox` exactly the size of the image and provide
 
 
     h3('Layout')
     h3('Layout')
 
 
-    with example(ui.card):
+    @example(ui.card)
+    def card_example():
         with ui.card().tight():
         with ui.card().tight():
             ui.image('http://placeimg.com/640/360/nature')
             ui.image('http://placeimg.com/640/360/nature')
             with ui.card_section():
             with ui.card_section():
                 ui.label('Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...')
                 ui.label('Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...')
 
 
-    with example(ui.column):
+    @example(ui.column)
+    def column_example():
         with ui.column():
         with ui.column():
             ui.label('label 1')
             ui.label('label 1')
             ui.label('label 2')
             ui.label('label 2')
             ui.label('label 3')
             ui.label('label 3')
 
 
-    with example(ui.row):
+    @example(ui.row)
+    def row_example():
         with ui.row():
         with ui.row():
             ui.label('label 1')
             ui.label('label 1')
             ui.label('label 2')
             ui.label('label 2')
             ui.label('label 3')
             ui.label('label 3')
 
 
-    clear_containers = '''#### Clear Containers
+    @example('''#### Clear Containers
 
 
 To remove all elements from a row, column or card container, use the `clear()` method.
 To remove all elements from a row, column or card container, use the `clear()` method.
 
 
 Alternatively, you can remove individual elements with `remove(element)`, where `element` is an Element or an index.
 Alternatively, you can remove individual elements with `remove(element)`, where `element` is an Element or an index.
-'''
-    with example(clear_containers):
+''')
+    def clear_containers_example():
         container = ui.row()
         container = ui.row()
 
 
         def add_face():
         def add_face():
@@ -403,11 +430,13 @@ Alternatively, you can remove individual elements with `remove(element)`, where
         ui.button('Remove', on_click=lambda: container.remove(0))
         ui.button('Remove', on_click=lambda: container.remove(0))
         ui.button('Clear', on_click=container.clear)
         ui.button('Clear', on_click=container.clear)
 
 
-    with example(ui.expansion):
+    @example(ui.expansion)
+    def expansion_example():
         with ui.expansion('Expand!', icon='work').classes('w-full'):
         with ui.expansion('Expand!', icon='work').classes('w-full'):
             ui.label('inside the expansion')
             ui.label('inside the expansion')
 
 
-    with example(ui.menu):
+    @example(ui.menu)
+    def menu_example():
         choice = ui.label('Try the menu.')
         choice = ui.label('Try the menu.')
         with ui.menu() as menu:
         with ui.menu() as menu:
             ui.menu_item('Menu item 1', lambda: choice.set_text('Selected item 1.'))
             ui.menu_item('Menu item 1', lambda: choice.set_text('Selected item 1.'))
@@ -418,31 +447,33 @@ Alternatively, you can remove individual elements with `remove(element)`, where
 
 
         ui.button('Open menu', on_click=menu.open)
         ui.button('Open menu', on_click=menu.open)
 
 
-    tooltips = '''#### Tooltips
+    @example('''#### Tooltips
 
 
 Simply call the `tooltip(text:str)` method on UI elements to provide a tooltip.
 Simply call the `tooltip(text:str)` method on UI elements to provide a tooltip.
-'''
-    with example(tooltips):
+''')
+    def tooltips_example():
         ui.label('Tooltips...').tooltip('...are shown on mouse over')
         ui.label('Tooltips...').tooltip('...are shown on mouse over')
         ui.button().props('icon=thumb_up').tooltip('I like this')
         ui.button().props('icon=thumb_up').tooltip('I like this')
 
 
-    with example(ui.notify):
+    @example(ui.notify)
+    def notify_example():
         ui.button('Say hi!', on_click=lambda: ui.notify('Hi!', close_button='OK'))
         ui.button('Say hi!', on_click=lambda: ui.notify('Hi!', close_button='OK'))
 
 
-    with example(ui.dialog):
+    @example(ui.dialog)
+    def dialog_example():
         with ui.dialog() as dialog, ui.card():
         with ui.dialog() as dialog, ui.card():
             ui.label('Hello world!')
             ui.label('Hello world!')
             ui.button('Close', on_click=dialog.close)
             ui.button('Close', on_click=dialog.close)
 
 
         ui.button('Open a dialog', on_click=dialog.open)
         ui.button('Open a dialog', on_click=dialog.open)
 
 
-    async_dialog = '''#### Awaitable dialog
+    @example('''#### Awaitable dialog
 
 
 Dialogs can be awaited.
 Dialogs can be awaited.
 Use the `submit` method to close the dialog and return a result.
 Use the `submit` method to close the dialog and return a result.
 Canceling the dialog by clicking in the background or pressing the escape key yields `None`.
 Canceling the dialog by clicking in the background or pressing the escape key yields `None`.
-'''
-    with example(async_dialog):
+''')
+    def async_dialog_example():
         with ui.dialog() as dialog, ui.card():
         with ui.dialog() as dialog, ui.card():
             ui.label('Are you sure?')
             ui.label('Are you sure?')
             with ui.row():
             with ui.row():
@@ -457,7 +488,7 @@ Canceling the dialog by clicking in the background or pressing the escape key yi
 
 
     h3('Appearance')
     h3('Appearance')
 
 
-    design = '''#### Styling
+    @example('''#### Styling
 
 
 NiceGUI uses the [Quasar Framework](https://quasar.dev/) version 1.0 and hence has its full design power.
 NiceGUI uses the [Quasar Framework](https://quasar.dev/) version 1.0 and hence has its full design power.
 Each NiceGUI element provides a `props` method whose content is passed [to the Quasar component](https://justpy.io/quasar_tutorial/introduction/#props-of-quasar-components):
 Each NiceGUI element provides a `props` method whose content is passed [to the Quasar component](https://justpy.io/quasar_tutorial/introduction/#props-of-quasar-components):
@@ -467,20 +498,21 @@ You can also apply [Tailwind](https://tailwindcss.com/) utility classes with the
 If you really need to apply CSS, you can use the `styles` method. Here the delimiter is `;` instead of a blank space.
 If you really need to apply CSS, you can use the `styles` method. Here the delimiter is `;` instead of a blank space.
 
 
 All three functions also provide `remove` and `replace` parameters in case the predefined look is not wanted in a particular styling.
 All three functions also provide `remove` and `replace` parameters in case the predefined look is not wanted in a particular styling.
-'''
-    with example(design):
+''')
+    def design_example():
         ui.radio(['x', 'y', 'z'], value='x').props('inline color=green')
         ui.radio(['x', 'y', 'z'], value='x').props('inline color=green')
         ui.button().props('icon=touch_app outline round').classes('shadow-lg')
         ui.button().props('icon=touch_app outline round').classes('shadow-lg')
         ui.label('Stylish!').style('color: #6E93D6; font-size: 200%; font-weight: 300')
         ui.label('Stylish!').style('color: #6E93D6; font-size: 200%; font-weight: 300')
 
 
-    with example(ui.colors):
+    @example(ui.colors)
+    def colors_example():
         ui.colors()
         ui.colors()
         ui.button('Default', on_click=lambda: ui.colors())
         ui.button('Default', on_click=lambda: ui.colors())
         ui.button('Gray', on_click=lambda: ui.colors(primary='#555'))
         ui.button('Gray', on_click=lambda: ui.colors(primary='#555'))
 
 
     h3('Action')
     h3('Action')
 
 
-    lifecycle = '''#### Lifecycle
+    @example('''#### Lifecycle
 
 
 You can run a function or coroutine as a parallel task by passing it to one of the following register methods:
 You can run a function or coroutine as a parallel task by passing it to one of the following register methods:
 
 
@@ -490,8 +522,9 @@ You can run a function or coroutine as a parallel task by passing it to one of t
 - `ui.on_disconnect`: Called when a client disconnects from NiceGUI. (Optional argument: socket)
 - `ui.on_disconnect`: Called when a client disconnects from NiceGUI. (Optional argument: socket)
 
 
 When NiceGUI is shut down or restarted, the startup tasks will be automatically canceled.
 When NiceGUI is shut down or restarted, the startup tasks will be automatically canceled.
-'''
-    with example(lifecycle):
+''')
+    def lifecycle_example():
+        global countdown
         import asyncio
         import asyncio
 
 
         l = ui.label()
         l = ui.label()
@@ -503,7 +536,8 @@ When NiceGUI is shut down or restarted, the startup tasks will be automatically
 
 
         # ui.on_connect(countdown)
         # ui.on_connect(countdown)
 
 
-    with example(ui.timer):
+    @example(ui.timer)
+    def timer_example():
         from datetime import datetime
         from datetime import datetime
 
 
         with ui.row().classes('items-center'):
         with ui.row().classes('items-center'):
@@ -520,7 +554,8 @@ When NiceGUI is shut down or restarted, the startup tasks will be automatically
             lazy_clock = ui.label()
             lazy_clock = ui.label()
             ui.timer(interval=0.1, callback=lazy_update)
             ui.timer(interval=0.1, callback=lazy_update)
 
 
-    with example(ui.keyboard):
+    @example(ui.keyboard)
+    def keyboard_example():
         from nicegui.events import KeyEventArguments
         from nicegui.events import KeyEventArguments
 
 
         def handle_key(e: KeyEventArguments):
         def handle_key(e: KeyEventArguments):
@@ -543,7 +578,7 @@ When NiceGUI is shut down or restarted, the startup tasks will be automatically
         ui.label('Key events can be caught globally by using the keyboard element.')
         ui.label('Key events can be caught globally by using the keyboard element.')
         ui.checkbox('Track key events').bind_value_to(keyboard, 'active')
         ui.checkbox('Track key events').bind_value_to(keyboard, 'active')
 
 
-    bindings = '''#### Bindings
+    @example('''#### Bindings
 
 
 NiceGUI is able to directly bind UI elements to models.
 NiceGUI is able to directly bind UI elements to models.
 Binding is possible for UI element properties like text, value or visibility and for model properties that are (nested) class attributes.
 Binding is possible for UI element properties like text, value or visibility and for model properties that are (nested) class attributes.
@@ -551,8 +586,8 @@ Binding is possible for UI element properties like text, value or visibility and
 Each element provides methods like `bind_value` and `bind_visibility` to create a two-way binding with the corresponding property.
 Each element provides methods like `bind_value` and `bind_visibility` to create a two-way binding with the corresponding property.
 To define a one-way binding use the `_from` and `_to` variants of these methods.
 To define a one-way binding use the `_from` and `_to` variants of these methods.
 Just pass a property of the model as parameter to these methods to create the binding.
 Just pass a property of the model as parameter to these methods to create the binding.
-'''
-    with example(bindings):
+''')
+    def bindings_example():
         class Demo:
         class Demo:
             def __init__(self):
             def __init__(self):
                 self.number = 1
                 self.number = 1
@@ -564,13 +599,13 @@ Just pass a property of the model as parameter to these methods to create the bi
             ui.toggle({1: 'a', 2: 'b', 3: 'c'}).bind_value(demo, 'number')
             ui.toggle({1: 'a', 2: 'b', 3: 'c'}).bind_value(demo, 'number')
             ui.number().bind_value(demo, 'number')
             ui.number().bind_value(demo, 'number')
 
 
-    ui_updates = '''#### UI Updates
+    @example('''#### UI Updates
 
 
 NiceGUI tries to automatically synchronize the state of UI elements with the client, e.g. when a label text, an input value or style/classes/props of an element have changed.
 NiceGUI tries to automatically synchronize the state of UI elements with the client, e.g. when a label text, an input value or style/classes/props of an element have changed.
 In other cases, you can explicitly call `element.update()` or `ui.update(*elements)` to update.
 In other cases, you can explicitly call `element.update()` or `ui.update(*elements)` to update.
 The example code shows both methods for a `ui.table`, where it is difficult to automatically detect changes in the `options` dictionary.
 The example code shows both methods for a `ui.table`, where it is difficult to automatically detect changes in the `options` dictionary.
-'''
-    with example(ui_updates):
+''')
+    def ui_updates_example():
         from random import randint
         from random import randint
 
 
         def add():
         def add():
@@ -585,13 +620,13 @@ The example code shows both methods for a `ui.table`, where it is difficult to a
         ui.button('Add', on_click=add)
         ui.button('Add', on_click=add)
         ui.button('Clear', on_click=clear)
         ui.button('Clear', on_click=clear)
 
 
-    async_handlers = '''#### Async event handlers
+    @example('''#### Async event handlers
 
 
 Most elements also support asynchronous event handlers.
 Most elements also support asynchronous event handlers.
 
 
 Note: You can also pass a `functools.partial` into the `on_click` property to wrap async functions with parameters.
 Note: You can also pass a `functools.partial` into the `on_click` property to wrap async functions with parameters.
-'''
-    with example(async_handlers):
+''')
+    def async_handlers_example():
         import asyncio
         import asyncio
 
 
         async def async_task():
         async def async_task():
@@ -603,7 +638,8 @@ Note: You can also pass a `functools.partial` into the `on_click` property to wr
 
 
     h3('Pages')
     h3('Pages')
 
 
-    with example(ui.page):
+    @example(ui.page)
+    def page_example():
         @ui.page('/other_page')
         @ui.page('/other_page')
         def other_page():
         def other_page():
             ui.label('Welcome to the other side')
             ui.label('Welcome to the other side')
@@ -617,7 +653,7 @@ Note: You can also pass a `functools.partial` into the `on_click` property to wr
         ui.link('Visit other page', other_page)
         ui.link('Visit other page', other_page)
         ui.link('Visit dark page', dark_page)
         ui.link('Visit dark page', dark_page)
 
 
-    shared_and_private_pages = '''#### Shared and Private Pages
+    @example('''#### Shared and Private Pages
 
 
 By default, pages created with the `@ui.page` decorator are "private".
 By default, pages created with the `@ui.page` decorator are "private".
 Their content is re-created for each client.
 Their content is re-created for each client.
@@ -631,8 +667,8 @@ Here, the displayed ID remains constant when the browser reloads the page.
 
 
 All elements that are not created within a decorated page function are automatically added to a new, *shared* index page at route "/".
 All elements that are not created within a decorated page function are automatically added to a new, *shared* index page at route "/".
 To make it "private" or to change other attributes like title, favicon etc. you can wrap it in a page function with `@ui.page('/', ...)` decorator.
 To make it "private" or to change other attributes like title, favicon etc. you can wrap it in a page function with `@ui.page('/', ...)` decorator.
-'''
-    with example(shared_and_private_pages):
+''')
+    def shared_and_private_pages_example():
         from uuid import uuid4
         from uuid import uuid4
 
 
         @ui.page('/private_page')
         @ui.page('/private_page')
@@ -646,20 +682,20 @@ To make it "private" or to change other attributes like title, favicon etc. you
         ui.link('private page', private_page)
         ui.link('private page', private_page)
         ui.link('shared page', shared_page)
         ui.link('shared page', shared_page)
 
 
-    page_with_path_parameters = '''#### Pages with Path Parameters
+    @example('''#### Pages with Path Parameters
 
 
 Page routes can contain parameters like [FastAPI](https://fastapi.tiangolo.com/tutorial/path-params/>).
 Page routes can contain parameters like [FastAPI](https://fastapi.tiangolo.com/tutorial/path-params/>).
 If type-annotated, they are automatically converted to bool, int, float and complex values.
 If type-annotated, they are automatically converted to bool, int, float and complex values.
 If the page function expects a `request` argument, the request object is automatically provided.
 If the page function expects a `request` argument, the request object is automatically provided.
-'''
-    with example(page_with_path_parameters):
+''')
+    def page_with_path_parameters_example():
         @ui.page('/repeat/{word}/{count}')
         @ui.page('/repeat/{word}/{count}')
         def page(word: str, count: int):
         def page(word: str, count: int):
             ui.label(word * count)
             ui.label(word * count)
 
 
         ui.link('Say hi to Santa!', 'repeat/Ho! /3')
         ui.link('Say hi to Santa!', 'repeat/Ho! /3')
 
 
-    yield_page_ready = '''#### Yielding for Page-Ready
+    @example('''#### Yielding for Page-Ready
 
 
 This is a handy alternative to the `on_page_ready` callback of the `@ui.page` decorator.
 This is a handy alternative to the `on_page_ready` callback of the `@ui.page` decorator.
 
 
@@ -669,8 +705,8 @@ Also it is possible to do async stuff while the user already sees the content wh
 
 
 The yield statement returns `nicegui.events.PageEvent`. 
 The yield statement returns `nicegui.events.PageEvent`. 
 This contains the websocket of the client.
 This contains the websocket of the client.
-    '''
-    with example(yield_page_ready):
+    ''')
+    def yield_page_ready_example():
         import asyncio
         import asyncio
         from typing import Generator
         from typing import Generator
 
 
@@ -687,7 +723,7 @@ This contains the websocket of the client.
 
 
         ui.link('show page-ready code after yield', '/yield_page_ready')
         ui.link('show page-ready code after yield', '/yield_page_ready')
 
 
-    page_layout = '''#### Page Layout
+    @example('''#### Page Layout
 
 
 With `ui.header`, `ui.footer`, `ui.left_drawer` and `ui.right_drawer` you can add additional layout elements to a page.
 With `ui.header`, `ui.footer`, `ui.left_drawer` and `ui.right_drawer` you can add additional layout elements to a page.
 The `fixed` argument controls whether the element should scroll or stay fixed on the screen.
 The `fixed` argument controls whether the element should scroll or stay fixed on the screen.
@@ -696,8 +732,8 @@ See <https://quasar.dev/layout/header-and-footer> and <https://quasar.dev/layout
 `elevated`, `bordered` and many more.
 `elevated`, `bordered` and many more.
 With `ui.page_sticky` you can place an element "sticky" on the screen.
 With `ui.page_sticky` you can place an element "sticky" on the screen.
 See <https://quasar.dev/layout/page-sticky> for more information.
 See <https://quasar.dev/layout/page-sticky> for more information.
-    '''
-    with example(page_layout):
+    ''')
+    def page_layout_example():
         @ui.page('/page_layout')
         @ui.page('/page_layout')
         async def page_layout():
         async def page_layout():
             ui.label('CONTENT')
             ui.label('CONTENT')
@@ -713,7 +749,8 @@ See <https://quasar.dev/layout/page-sticky> for more information.
 
 
         ui.link('show page with fancy layout', page_layout)
         ui.link('show page with fancy layout', page_layout)
 
 
-    with example(ui.open):
+    @example(ui.open)
+    def ui_open_example():
         @ui.page('/yet_another_page')
         @ui.page('/yet_another_page')
         def yet_another_page():
         def yet_another_page():
             ui.label('Welcome to yet another page')
             ui.label('Welcome to yet another page')
@@ -721,15 +758,15 @@ See <https://quasar.dev/layout/page-sticky> for more information.
 
 
         ui.button('REDIRECT', on_click=lambda e: ui.open(yet_another_page, e.socket))
         ui.button('REDIRECT', on_click=lambda e: ui.open(yet_another_page, e.socket))
 
 
-    sessions = '''#### Sessions
+    @example('''#### Sessions
 
 
 `ui.page` provides an optional `on_connect` argument to register a callback.
 `ui.page` provides an optional `on_connect` argument to register a callback.
 It is invoked for each new connection to the page.
 It is invoked for each new connection to the page.
 
 
 The optional `request` argument provides insights about the clients URL parameters etc. (see [the JustPy docs](https://justpy.io/tutorial/request_object/) for more details).
 The optional `request` argument provides insights about the clients URL parameters etc. (see [the JustPy docs](https://justpy.io/tutorial/request_object/) for more details).
 It also enables you to identify sessions over [longer time spans by configuring cookies](https://justpy.io/tutorial/sessions/).
 It also enables you to identify sessions over [longer time spans by configuring cookies](https://justpy.io/tutorial/sessions/).
-'''
-    with example(sessions):
+''')
+    def sessions_example():
         from collections import Counter
         from collections import Counter
         from datetime import datetime
         from datetime import datetime
 
 
@@ -749,14 +786,14 @@ It also enables you to identify sessions over [longer time spans by configuring
 
 
         ui.link('Visit session demo', session_demo)
         ui.link('Visit session demo', session_demo)
 
 
-    javascript = '''#### JavaScript
+    @example('''#### JavaScript
 
 
 With `ui.run_javascript()` you can run arbitrary JavaScript code on a page that is executed in the browser.
 With `ui.run_javascript()` you can run arbitrary JavaScript code on a page that is executed in the browser.
 The asynchronous function will return after the command(s) are executed.
 The asynchronous function will return after the command(s) are executed.
 The result of the execution is returned as a dictionary containing the response string per websocket.
 The result of the execution is returned as a dictionary containing the response string per websocket.
 You can also set `respond=False` to send a command without waiting for a response.
 You can also set `respond=False` to send a command without waiting for a response.
-'''
-    with example(javascript):
+''')
+    def javascript_example():
         async def alert():
         async def alert():
             await ui.run_javascript('alert("Hello!")', respond=False)
             await ui.run_javascript('alert("Hello!")', respond=False)
 
 
@@ -770,7 +807,8 @@ You can also set `respond=False` to send a command without waiting for a respons
 
 
     h3('Routes')
     h3('Routes')
 
 
-    with example(ui.get):
+    @example(ui.get)
+    def get_example():
         from starlette import requests, responses
         from starlette import requests, responses
 
 
         @ui.get('/another/route/{id}')
         @ui.get('/another/route/{id}')
@@ -779,13 +817,15 @@ You can also set `respond=False` to send a command without waiting for a respons
 
 
         ui.link('Try yet another route!', 'another/route/42')
         ui.link('Try yet another route!', 'another/route/42')
 
 
-    with example(ui.add_static_files):
+    @example(ui.add_static_files)
+    def add_static_files_example():
         ui.add_static_files('/examples', 'examples')
         ui.add_static_files('/examples', 'examples')
         ui.link('Slideshow Example (raw file)', 'examples/slideshow/main.py')
         ui.link('Slideshow Example (raw file)', 'examples/slideshow/main.py')
         with ui.image('examples/slideshow/slides/slide1.jpg'):
         with ui.image('examples/slideshow/slides/slide1.jpg'):
             ui.label('first image from slideshow').classes('absolute-bottom text-subtitle2')
             ui.label('first image from slideshow').classes('absolute-bottom text-subtitle2')
 
 
-    with example(ui.add_route):
+    @example(ui.add_route)
+    def add_route_example():
         import starlette
         import starlette
 
 
         ui.add_route(starlette.routing.Route(
         ui.add_route(starlette.routing.Route(
@@ -796,7 +836,7 @@ You can also set `respond=False` to send a command without waiting for a respons
 
 
     h3('Configuration')
     h3('Configuration')
 
 
-    ui_run = '''#### ui.run
+    @example('''#### ui.run
 
 
 You can call `ui.run()` with optional arguments:
 You can call `ui.run()` with optional arguments:
 
 
@@ -820,8 +860,8 @@ The environment variables `HOST` and `PORT` can also be used to configure NiceGU
 
 
 To avoid the potentially costly import of Matplotlib, you set the environment variable `MATPLOTLIB=false`.
 To avoid the potentially costly import of Matplotlib, you set the environment variable `MATPLOTLIB=false`.
 This will make `ui.plot` and `ui.line_plot` unavailable.
 This will make `ui.plot` and `ui.line_plot` unavailable.
-'''
-    with example(ui_run):
+''')
+    def ui_run_example():
         ui.label('dark page on port 7000 without reloading')
         ui.label('dark page on port 7000 without reloading')
 
 
         # ui.run(dark=True, port=7000, reload=False)
         # ui.run(dark=True, port=7000, reload=False)