Skip to content

Commit

Permalink
Feature/2 (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-yin authored Sep 18, 2024
1 parent 187f3c7 commit b39c4b0
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 37 deletions.
53 changes: 45 additions & 8 deletions docs/source/slot.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ Notes:

This is the **killer feature**, so please read it carefully.

### Component argument in RendersOneField

Let's update the `BlogComponent` again

```python
Expand Down Expand Up @@ -198,7 +200,7 @@ Notes:
1. We do not need to store the `classes` to the `BlogComponent` and then pass it to the `HeaderComponent`, just set `component='header'` in the `RendersOneField` field, the `HeaderComponent` would receive the `classes` argument automatically
2. If you check the template code in the `BlogComponent`, `{{ self.header.value }}` ia very simple to help you understand what it is.

## Component with RendersManyField
### Component argument in RendersManyField

If you have

Expand Down Expand Up @@ -245,14 +247,48 @@ With `component` argument, we can **connect** components together, in clean way.

![](./images/blog-components.png)

## Component argument in slot field

`component` in `RendersOneField` or `RendersManyField` supports many variable types.
## Component argument in slot fields supports different variable types

### Component registered name

```python
header = RendersOneField(required=True, component="header")
@component.register("header")
class HeaderComponent(component.Component):
def __init__(self, classes, **kwargs):
self.classes = classes

template = """
<h1 class="{{ self.classes }}">
{{ self.content }}
</h1>
"""


@component.register("post")
class PostComponent(component.Component):
def __init__(self, post, **kwargs):
self.post = post

template = """
{% load viewcomponent_tags %}
<h1>{{ self.post.title }}</h1>
<div>{{ self.post.description }}</div>
"""


@component.register("blog")
class BlogComponent(component.Component):
header = RendersOneField(required=True, component="header")
posts = RendersManyField(required=True, component="post")

template = """
{% load viewcomponent_tags %}
{{ self.header.value }}
{% for post in self.posts.value %}
{{ post }}
{% endfor %}
"""
```

### Component class
Expand Down Expand Up @@ -301,7 +337,7 @@ class BlogComponent(component.Component):
header = RendersOneField(required=True, component="header")
posts = RendersManyField(
required=True,
component=lambda post, **kwargs: mark_safe(
component=lambda self, post, **kwargs: mark_safe(
f"""
<h1>{post.title}</h1>
<div>{post.description}</div>
Expand All @@ -321,17 +357,18 @@ class BlogComponent(component.Component):
Notes:

1. Here we use lambda function to return string from the `post` variable, so we do not need to create a Component.
2. We can still use `self.xxx` to access value of the blog component.

### Function which return component instance

We can use function to return instance of a component.
We can use function to return instance of a component, this is useful when we need to pass some special default values to the other component.

```python
class BlogComponent(component.Component):
header = RendersOneField(required=True, component="header")
posts = RendersManyField(
required=True,
component=lambda post: PostComponent(post=post),
component=lambda post, **kwargs: PostComponent(post=post),
)

template = """
Expand Down
49 changes: 34 additions & 15 deletions src/django_viewcomponent/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@
class FieldValue:
def __init__(
self,
content: str,
nodelist,
dict_data: dict,
component: None,
parent_component=None,
):
self._content = content or ""
self._nodelist = nodelist
self._dict_data = dict_data
self._component = component
self._parent_component = parent_component

def __str__(self):
if self._component is None:
return self._content
else:
# If the slot field is defined with component, then we will use the component to render
return self.render()
return self.render()

def render(self):
from django_viewcomponent.component import Component
Expand All @@ -31,7 +27,10 @@ def render(self):
elif not isinstance(self._component, type) and callable(self._component):
# self._component is function
callable_component = self._component
result = callable_component(**self._dict_data)
result = callable_component(
self=self._parent_component,
**self._dict_data,
)

if isinstance(result, str):
return result
Expand All @@ -48,6 +47,8 @@ def render(self):
):
# self._component is Component class
return self._render_for_component_cls(self._component)
elif self._component is None:
return self._nodelist.render(self._parent_component.component_context)
else:
raise ValueError(f"Invalid component variable {self._component}")

Expand All @@ -67,7 +68,7 @@ def _render_for_component_instance(self, component):
# create slot fields
component.create_slot_fields()

component.content = self._content
component.content = self._nodelist.render(updated_context)

component.check_slot_fields()

Expand Down Expand Up @@ -101,14 +102,14 @@ def filled(self):
def required(self):
return self._required

def handle_call(self, content, **kwargs):
def handle_call(self, nodelist, **kwargs):
raise NotImplementedError("You must implement the `handle_call` method.")


class RendersOneField(BaseSlotField):
def handle_call(self, content, **kwargs):
def handle_call(self, nodelist, **kwargs):
value_instance = FieldValue(
content=content,
nodelist=nodelist,
dict_data={**kwargs},
component=self._component,
parent_component=self.parent_component,
Expand All @@ -118,17 +119,35 @@ def handle_call(self, content, **kwargs):
self._value = value_instance


class FieldValueListWrapper:
"""
This helps render FieldValue eagerly when component template has
{% for panel in self.panels.value %}, this can avoid issues if `panel` of the for loop statement
# override context variables in some cases.
"""

def __init__(self):
self.data = []

def append(self, value):
self.data.append(value)

def __iter__(self):
for field_value in self.data:
yield field_value.render()


class RendersManyField(BaseSlotField):
def handle_call(self, content, **kwargs):
def handle_call(self, nodelist, **kwargs):
value_instance = FieldValue(
content=content,
nodelist=nodelist,
dict_data={**kwargs},
component=self._component,
parent_component=self.parent_component,
)

if self._value is None:
self._value = []
self._value = FieldValueListWrapper()

self._value.append(value_instance)
self._filled = True
4 changes: 1 addition & 3 deletions src/django_viewcomponent/templatetags/viewcomponent_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ def __repr__(self):
raise NotImplementedError

def render(self, context):
content = self.nodelist.render(context)

resolved_kwargs = {
key: safe_resolve(kwarg, context) for key, kwarg in self.kwargs.items()
}
Expand All @@ -76,7 +74,7 @@ def render(self, context):
"The 'content' kwarg is reserved and cannot be passed in component call tag",
)

resolved_kwargs["content"] = content
resolved_kwargs["nodelist"] = self.nodelist

component_token, field_token = self.args[0].token.split(".")
component_instance = FilterExpression(component_token, self.parser).resolve(
Expand Down
Loading

0 comments on commit b39c4b0

Please sign in to comment.