Skip to content

Commit b39c4b0

Browse files
authored
Feature/2 (#14)
1 parent 187f3c7 commit b39c4b0

File tree

4 files changed

+191
-37
lines changed

4 files changed

+191
-37
lines changed

docs/source/slot.md

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ Notes:
122122

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

125+
### Component argument in RendersOneField
126+
125127
Let's update the `BlogComponent` again
126128

127129
```python
@@ -198,7 +200,7 @@ Notes:
198200
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
199201
2. If you check the template code in the `BlogComponent`, `{{ self.header.value }}` ia very simple to help you understand what it is.
200202

201-
## Component with RendersManyField
203+
### Component argument in RendersManyField
202204

203205
If you have
204206

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

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

248-
## Component argument in slot field
249-
250-
`component` in `RendersOneField` or `RendersManyField` supports many variable types.
250+
## Component argument in slot fields supports different variable types
251251

252252
### Component registered name
253253

254254
```python
255-
header = RendersOneField(required=True, component="header")
255+
@component.register("header")
256+
class HeaderComponent(component.Component):
257+
def __init__(self, classes, **kwargs):
258+
self.classes = classes
259+
260+
template = """
261+
<h1 class="{{ self.classes }}">
262+
{{ self.content }}
263+
</h1>
264+
"""
265+
266+
267+
@component.register("post")
268+
class PostComponent(component.Component):
269+
def __init__(self, post, **kwargs):
270+
self.post = post
271+
272+
template = """
273+
{% load viewcomponent_tags %}
274+
275+
<h1>{{ self.post.title }}</h1>
276+
<div>{{ self.post.description }}</div>
277+
"""
278+
279+
280+
@component.register("blog")
281+
class BlogComponent(component.Component):
282+
header = RendersOneField(required=True, component="header")
283+
posts = RendersManyField(required=True, component="post")
284+
285+
template = """
286+
{% load viewcomponent_tags %}
287+
{{ self.header.value }}
288+
{% for post in self.posts.value %}
289+
{{ post }}
290+
{% endfor %}
291+
"""
256292
```
257293

258294
### Component class
@@ -301,7 +337,7 @@ class BlogComponent(component.Component):
301337
header = RendersOneField(required=True, component="header")
302338
posts = RendersManyField(
303339
required=True,
304-
component=lambda post, **kwargs: mark_safe(
340+
component=lambda self, post, **kwargs: mark_safe(
305341
f"""
306342
<h1>{post.title}</h1>
307343
<div>{post.description}</div>
@@ -321,17 +357,18 @@ class BlogComponent(component.Component):
321357
Notes:
322358

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

325362
### Function which return component instance
326363

327-
We can use function to return instance of a component.
364+
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.
328365

329366
```python
330367
class BlogComponent(component.Component):
331368
header = RendersOneField(required=True, component="header")
332369
posts = RendersManyField(
333370
required=True,
334-
component=lambda post: PostComponent(post=post),
371+
component=lambda post, **kwargs: PostComponent(post=post),
335372
)
336373

337374
template = """

src/django_viewcomponent/fields.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,18 @@
44
class FieldValue:
55
def __init__(
66
self,
7-
content: str,
7+
nodelist,
88
dict_data: dict,
99
component: None,
1010
parent_component=None,
1111
):
12-
self._content = content or ""
12+
self._nodelist = nodelist
1313
self._dict_data = dict_data
1414
self._component = component
1515
self._parent_component = parent_component
1616

1717
def __str__(self):
18-
if self._component is None:
19-
return self._content
20-
else:
21-
# If the slot field is defined with component, then we will use the component to render
22-
return self.render()
18+
return self.render()
2319

2420
def render(self):
2521
from django_viewcomponent.component import Component
@@ -31,7 +27,10 @@ def render(self):
3127
elif not isinstance(self._component, type) and callable(self._component):
3228
# self._component is function
3329
callable_component = self._component
34-
result = callable_component(**self._dict_data)
30+
result = callable_component(
31+
self=self._parent_component,
32+
**self._dict_data,
33+
)
3534

3635
if isinstance(result, str):
3736
return result
@@ -48,6 +47,8 @@ def render(self):
4847
):
4948
# self._component is Component class
5049
return self._render_for_component_cls(self._component)
50+
elif self._component is None:
51+
return self._nodelist.render(self._parent_component.component_context)
5152
else:
5253
raise ValueError(f"Invalid component variable {self._component}")
5354

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

70-
component.content = self._content
71+
component.content = self._nodelist.render(updated_context)
7172

7273
component.check_slot_fields()
7374

@@ -101,14 +102,14 @@ def filled(self):
101102
def required(self):
102103
return self._required
103104

104-
def handle_call(self, content, **kwargs):
105+
def handle_call(self, nodelist, **kwargs):
105106
raise NotImplementedError("You must implement the `handle_call` method.")
106107

107108

108109
class RendersOneField(BaseSlotField):
109-
def handle_call(self, content, **kwargs):
110+
def handle_call(self, nodelist, **kwargs):
110111
value_instance = FieldValue(
111-
content=content,
112+
nodelist=nodelist,
112113
dict_data={**kwargs},
113114
component=self._component,
114115
parent_component=self.parent_component,
@@ -118,17 +119,35 @@ def handle_call(self, content, **kwargs):
118119
self._value = value_instance
119120

120121

122+
class FieldValueListWrapper:
123+
"""
124+
This helps render FieldValue eagerly when component template has
125+
{% for panel in self.panels.value %}, this can avoid issues if `panel` of the for loop statement
126+
# override context variables in some cases.
127+
"""
128+
129+
def __init__(self):
130+
self.data = []
131+
132+
def append(self, value):
133+
self.data.append(value)
134+
135+
def __iter__(self):
136+
for field_value in self.data:
137+
yield field_value.render()
138+
139+
121140
class RendersManyField(BaseSlotField):
122-
def handle_call(self, content, **kwargs):
141+
def handle_call(self, nodelist, **kwargs):
123142
value_instance = FieldValue(
124-
content=content,
143+
nodelist=nodelist,
125144
dict_data={**kwargs},
126145
component=self._component,
127146
parent_component=self.parent_component,
128147
)
129148

130149
if self._value is None:
131-
self._value = []
150+
self._value = FieldValueListWrapper()
132151

133152
self._value.append(value_instance)
134153
self._filled = True

src/django_viewcomponent/templatetags/viewcomponent_tags.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ def __repr__(self):
6565
raise NotImplementedError
6666

6767
def render(self, context):
68-
content = self.nodelist.render(context)
69-
7068
resolved_kwargs = {
7169
key: safe_resolve(kwarg, context) for key, kwarg in self.kwargs.items()
7270
}
@@ -76,7 +74,7 @@ def render(self, context):
7674
"The 'content' kwarg is reserved and cannot be passed in component call tag",
7775
)
7876

79-
resolved_kwargs["content"] = content
77+
resolved_kwargs["nodelist"] = self.nodelist
8078

8179
component_token, field_token = self.args[0].token.split(".")
8280
component_instance = FilterExpression(component_token, self.parser).resolve(

0 commit comments

Comments
 (0)