diff --git a/conftest.py b/conftest.py index 2646fdb3..103104a1 100644 --- a/conftest.py +++ b/conftest.py @@ -8,6 +8,9 @@ def pytest_configure(): { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": ["tests"], + "OPTIONS": { + "libraries": {"unicorn": "django_unicorn.templatetags.unicorn",} + }, } ] databases = {"default": {"ENGINE": "django.db.backends.sqlite3",}} diff --git a/django_unicorn/components/unicorn_template_response.py b/django_unicorn/components/unicorn_template_response.py index 09c342d4..5870abfa 100644 --- a/django_unicorn/components/unicorn_template_response.py +++ b/django_unicorn/components/unicorn_template_response.py @@ -95,17 +95,20 @@ def render(self): json_tag["id"] = json_element_id json_tag.string = sanitize_html(init) + # Include init script and json tags from child components + json_tags = [json_tag] + for child in self.component.children: + if hasattr(child, "_init_script"): + init_script = f"{init_script} {child._init_script}" + if hasattr(child, "_json_tags"): + json_tags.extend(child._json_tags) + + # Defer rendering the init script and json tag until the outermost + # component (without a parent) is rendered if self.component.parent: self.component._init_script = init_script - self.component._json_tag = json_tag + self.component._json_tags = json_tags else: - json_tags = [] - json_tags.append(json_tag) - - for child in self.component.children: - init_script = f"{init_script} {child._init_script}" - json_tags.append(child._json_tag) - script_tag = soup.new_tag("script") script_tag["type"] = "module" script_tag.string = f"if (typeof Unicorn === 'undefined') {{ console.error('Unicorn is missing. Do you need {{% load unicorn %}} or {{% unicorn_scripts %}}?') }} else {{ {init_script} }}" diff --git a/example/unicorn/components/nested/actions.py b/example/unicorn/components/nested/actions.py new file mode 100644 index 00000000..43fbcd2e --- /dev/null +++ b/example/unicorn/components/nested/actions.py @@ -0,0 +1,14 @@ +from django_unicorn.components import UnicornView +from typing import Callable + + +class ActionsView(UnicornView): + # In this example the ActionsView is completely controlled by the + # RowView and it doesn't really "own" these - its useful to put them + # on the class for type hints and/or default values, but we don't + # want to give the impression they are ActionsView's own state + + is_editing: bool + on_edit: Callable + on_cancel: Callable + on_save: Callable \ No newline at end of file diff --git a/example/unicorn/components/nested/row.py b/example/unicorn/components/nested/row.py index bf31a7c9..c119aa31 100644 --- a/example/unicorn/components/nested/row.py +++ b/example/unicorn/components/nested/row.py @@ -2,16 +2,31 @@ from example.coffee.models import Flavor +def callback(func): + """A decorator for callbacks passed as kwargs to a child component + + This allows the bound method itself to be resolved in the template. + Without it, the template variable resolver will automatically call + the method and use what is returned as the resolved value. + """ + func.do_not_call_in_templates = True + return func + + class RowView(UnicornView): model: Flavor = None is_editing = False - def edit(self): + @callback + def on_edit(self): + print("on_edit callback fired") self.is_editing = True - def cancel(self): + @callback + def on_cancel(self): self.is_editing = False - def save(self): + @callback + def on_save(self): self.model.save() - self.is_editing = False + self.is_editing = False \ No newline at end of file diff --git a/example/unicorn/templates/unicorn/nested/actions.html b/example/unicorn/templates/unicorn/nested/actions.html new file mode 100644 index 00000000..ca8e964c --- /dev/null +++ b/example/unicorn/templates/unicorn/nested/actions.html @@ -0,0 +1,9 @@ +