mirror of
https://github.com/django-components/django-components.git
synced 2025-08-08 08:17:59 +00:00
docs: add advanced topics
This commit is contained in:
parent
ab0b5c6266
commit
052945415e
3 changed files with 407 additions and 0 deletions
|
@ -14,3 +14,6 @@
|
|||
* [Settings](integration/settings.md)
|
||||
* [CLI Commands](integration/commands.md)
|
||||
* [Logging and Debugging](integration/logging_debugging.md)
|
||||
* Advanced topics
|
||||
* [Slot rendering](advanced/slot_rendering.md)
|
||||
* [Slots and Blocks](advanced/slots_and_blocks.md)
|
||||
|
|
238
docs/user_guide/advanced/slot_rendering.md
Normal file
238
docs/user_guide/advanced/slot_rendering.md
Normal file
|
@ -0,0 +1,238 @@
|
|||
# Slot rendering
|
||||
|
||||
This doc serves as a primer on how component slots and fills are resolved.
|
||||
|
||||
## Flow
|
||||
|
||||
1. Imagine you have a template. Some kind of text, maybe HTML:
|
||||
```django
|
||||
| ------
|
||||
| ---------
|
||||
| ----
|
||||
| -------
|
||||
```
|
||||
|
||||
2. The template may contain some vars, tags, etc
|
||||
```django
|
||||
| -- {{ my_var }} --
|
||||
| ---------
|
||||
| ----
|
||||
| -------
|
||||
```
|
||||
|
||||
3. The template also contains some slots, etc
|
||||
```django
|
||||
| -- {{ my_var }} --
|
||||
| ---------
|
||||
| -- {% slot "myslot" %} ---
|
||||
| -- {% endslot %} ---
|
||||
| ----
|
||||
| -- {% slot "myslot2" %} ---
|
||||
| -- {% endslot %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
4. Slots may be nested
|
||||
```django
|
||||
| -- {{ my_var }} --
|
||||
| -- ABC
|
||||
| -- {% slot "myslot" %} ---
|
||||
| ----- DEF {{ my_var }}
|
||||
| ----- {% slot "myslot_inner" %}
|
||||
| -------- GHI {{ my_var }}
|
||||
| ----- {% endslot %}
|
||||
| -- {% endslot %} ---
|
||||
| ----
|
||||
| -- {% slot "myslot2" %} ---
|
||||
| ---- JKL {{ my_var }}
|
||||
| -- {% endslot %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
5. Some slots may be inside fills for other components
|
||||
```django
|
||||
| -- {{ my_var }} --
|
||||
| -- ABC
|
||||
| -- {% slot "myslot" %}---
|
||||
| ----- DEF {{ my_var }}
|
||||
| ----- {% slot "myslot_inner" %}
|
||||
| -------- GHI {{ my_var }}
|
||||
| ----- {% endslot %}
|
||||
| -- {% endslot %} ---
|
||||
| ------
|
||||
| -- {% component "mycomp" %} ---
|
||||
| ---- {% slot "myslot" %} ---
|
||||
| ------- JKL {{ my_var }}
|
||||
| ------- {% slot "myslot_inner" %}
|
||||
| ---------- MNO {{ my_var }}
|
||||
| ------- {% endslot %}
|
||||
| ---- {% endslot %} ---
|
||||
| -- {% endcomponent %} ---
|
||||
| ----
|
||||
| -- {% slot "myslot2" %} ---
|
||||
| ---- PQR {{ my_var }}
|
||||
| -- {% endslot %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
5. I want to render the slots with `{% fill %}` tag that were defined OUTSIDE of this template. How do I do that?
|
||||
|
||||
1. Traverse the template to collect ALL slots
|
||||
- NOTE: I will also look inside `{% slot %}` and `{% fill %}` tags, since they are all still
|
||||
defined within the same TEMPLATE.
|
||||
|
||||
I should end up with a list like this:
|
||||
```txt
|
||||
- Name: "myslot"
|
||||
ID 0001
|
||||
Content:
|
||||
| ----- DEF {{ my_var }}
|
||||
| ----- {% slot "myslot_inner" %}
|
||||
| -------- GHI {{ my_var }}
|
||||
| ----- {% endslot %}
|
||||
- Name: "myslot_inner"
|
||||
ID 0002
|
||||
Content:
|
||||
| -------- GHI {{ my_var }}
|
||||
- Name: "myslot"
|
||||
ID 0003
|
||||
Content:
|
||||
| ------- JKL {{ my_var }}
|
||||
| ------- {% slot "myslot_inner" %}
|
||||
| ---------- MNO {{ my_var }}
|
||||
| ------- {% endslot %}
|
||||
- Name: "myslot_inner"
|
||||
ID 0004
|
||||
Content:
|
||||
| ---------- MNO {{ my_var }}
|
||||
- Name: "myslot2"
|
||||
ID 0005
|
||||
Content:
|
||||
| ---- PQR {{ my_var }}
|
||||
```
|
||||
|
||||
2. Note the relationships - which slot is nested in which one
|
||||
|
||||
I should end up with a graph-like data like:
|
||||
```txt
|
||||
- 0001: [0002]
|
||||
- 0002: []
|
||||
- 0003: [0004]
|
||||
- 0004: []
|
||||
- 0005: []
|
||||
```
|
||||
|
||||
In other words, the data tells us that slot ID `0001` is PARENT of slot `0002`.
|
||||
|
||||
This is important, because, IF parent template provides slot fill for slot 0001,
|
||||
then we DON'T NEED TO render it's children, AKA slot 0002.
|
||||
|
||||
3. Find roots of the slot relationships
|
||||
|
||||
The data from previous step can be understood also as a collection of
|
||||
directled acyclig graphs (DAG), e.g.:
|
||||
|
||||
```txt
|
||||
0001 --> 0002
|
||||
0003 --> 0004
|
||||
0005
|
||||
```
|
||||
|
||||
So we find the roots (`0001`, `0003`, `0005`), AKA slots that are NOT nested in other slots.
|
||||
We do so by going over ALL entries from previous step. Those IDs which are NOT
|
||||
mentioned in ANY of the lists are the roots.
|
||||
|
||||
Because of the nature of nested structures, there cannot be any cycles.
|
||||
|
||||
4. Recursively render slots, starting from roots.
|
||||
1. First we take each of the roots.
|
||||
|
||||
2. Then we check if there is a slot fill for given slot name.
|
||||
|
||||
3. If YES we replace the slot node with the fill node.
|
||||
- Note: We assume slot fills are ALREADY RENDERED!
|
||||
```django
|
||||
| ----- {% slot "myslot_inner" %}
|
||||
| -------- GHI {{ my_var }}
|
||||
| ----- {% endslot %}
|
||||
```
|
||||
becomes
|
||||
```django
|
||||
| ----- Bla bla
|
||||
| -------- Some Other Content
|
||||
| ----- ...
|
||||
```
|
||||
We don't continue further, because inner slots have been overriden!
|
||||
|
||||
4. If NO, then we will replace slot nodes with their children, e.g.:
|
||||
```django
|
||||
| ---- {% slot "myslot" %} ---
|
||||
| ------- JKL {{ my_var }}
|
||||
| ------- {% slot "myslot_inner" %}
|
||||
| ---------- MNO {{ my_var }}
|
||||
| ------- {% endslot %}
|
||||
| ---- {% endslot %} ---
|
||||
```
|
||||
Becomes
|
||||
```django
|
||||
| ------- JKL {{ my_var }}
|
||||
| ------- {% slot "myslot_inner" %}
|
||||
| ---------- MNO {{ my_var }}
|
||||
| ------- {% endslot %}
|
||||
```
|
||||
|
||||
5. We check if the slot includes any children `{% slot %}` tags. If YES, then continue with step 4. for them, and wait until they finish.
|
||||
|
||||
5. At this point, ALL slots should be rendered and we should have something like this:
|
||||
```django
|
||||
| -- {{ my_var }} --
|
||||
| -- ABC
|
||||
| ----- DEF {{ my_var }}
|
||||
| -------- GHI {{ my_var }}
|
||||
| ------
|
||||
| -- {% component "mycomp" %} ---
|
||||
| ------- JKL {{ my_var }}
|
||||
| ---- {% component "mycomp" %} ---
|
||||
| ---------- MNO {{ my_var }}
|
||||
| ---- {% endcomponent %} ---
|
||||
| -- {% endcomponent %} ---
|
||||
| ----
|
||||
| -- {% component "mycomp2" %} ---
|
||||
| ---- PQR {{ my_var }}
|
||||
| -- {% endcomponent %} ---
|
||||
| ----
|
||||
```
|
||||
- NOTE: Inserting fills into {% slots %} should NOT introduce new {% slots %}, as the fills should be already rendered!
|
||||
|
||||
## Using the correct context in {% slot/fill %} tags
|
||||
|
||||
In previous section, we said that the `{% fill %}` tags should be already rendered by the time they are inserted into the `{% slot %}` tags.
|
||||
|
||||
This is not quite true. To help you understand, consider this complex case:
|
||||
|
||||
```django
|
||||
| -- {% for var in [1, 2, 3] %} ---
|
||||
| ---- {% component "mycomp2" %} ---
|
||||
| ------ {% fill "first" %}
|
||||
| ------- STU {{ my_var }}
|
||||
| ------- {{ var }}
|
||||
| ------ {% endfill %}
|
||||
| ------ {% fill "second" %}
|
||||
| -------- {% component var=var my_var=my_var %}
|
||||
| ---------- VWX {{ my_var }}
|
||||
| -------- {% endcomponent %}
|
||||
| ------ {% endfill %}
|
||||
| ---- {% endcomponent %} ---
|
||||
| -- {% endfor %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
We want the forloop variables to be available inside the `{% fill %}` tags. Because of that, however, we CANNOT render the fills/slots in advance.
|
||||
|
||||
Instead, our solution is closer to [how Vue handles slots](https://vuejs.org/guide/components/slots.html#scoped-slots). In Vue, slots are effectively functions that accept a context variables and render some content.
|
||||
|
||||
While we do not wrap the logic in a function, we do PREPARE IN ADVANCE:
|
||||
1. The content that should be rendered for each slot
|
||||
2. The context variables from `get_context_data()`
|
||||
|
||||
Thus, once we reach the `{% slot %}` node, in it's `render()` method, we access the data above, and, depending on the `context_behavior` setting, include the current context or not. For more info, see `SlotNode.render()`.
|
166
docs/user_guide/advanced/slots_and_blocks.md
Normal file
166
docs/user_guide/advanced/slots_and_blocks.md
Normal file
|
@ -0,0 +1,166 @@
|
|||
# Using `slot` and `block` tags
|
||||
|
||||
1. First let's clarify how `include` and `extends` tags work inside components.
|
||||
So when component template includes `include` or `extends` tags, it's as if the "included"
|
||||
template was inlined. So if the "included" template contains `slot` tags, then the component
|
||||
uses those slots.
|
||||
|
||||
So if you have a template `abc.html`:
|
||||
```django
|
||||
<div>
|
||||
hello
|
||||
{% slot "body" %}{% endslot %}
|
||||
</div>
|
||||
```
|
||||
|
||||
And components that make use of `abc.html` via `include` or `extends`:
|
||||
```py
|
||||
@component.register("my_comp_extends")
|
||||
class MyCompWithExtends(component.Component):
|
||||
template = """{% extends "abc.html" %}"""
|
||||
|
||||
@component.register("my_comp_include")
|
||||
class MyCompWithInclude(component.Component):
|
||||
template = """{% include "abc.html" %}"""
|
||||
```
|
||||
|
||||
Then you can set slot fill for the slot imported via `include/extends`:
|
||||
|
||||
```django
|
||||
{% component "my_comp_extends" %}
|
||||
{% fill "body" %}
|
||||
123
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
And it will render:
|
||||
```html
|
||||
<div>
|
||||
hello
|
||||
123
|
||||
</div>
|
||||
```
|
||||
|
||||
2. Slot and block
|
||||
|
||||
So if you have a template `abc.html` like so:
|
||||
|
||||
```django
|
||||
<div>
|
||||
hello
|
||||
{% block inner %}
|
||||
1
|
||||
{% slot "body" %}
|
||||
2
|
||||
{% endslot %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
```
|
||||
|
||||
and component `my_comp`:
|
||||
|
||||
```py
|
||||
@component.register("my_comp")
|
||||
class MyComp(component.Component):
|
||||
template_name = "abc.html"
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
1. Since the `block` wasn't overriden, you can use the `body` slot:
|
||||
|
||||
```django
|
||||
{% component "my_comp" %}
|
||||
{% fill "body" %}
|
||||
XYZ
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
And we get:
|
||||
|
||||
```html
|
||||
<div>hello 1 XYZ</div>
|
||||
```
|
||||
|
||||
2. `blocks` CANNOT be overriden through the `component` tag, so something like this:
|
||||
|
||||
```django
|
||||
{% component "my_comp" %}
|
||||
{% fill "body" %}
|
||||
XYZ
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
{% block "inner" %}
|
||||
456
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
Will still render the component content just the same:
|
||||
|
||||
```html
|
||||
<div>hello 1 XYZ</div>
|
||||
```
|
||||
|
||||
3. You CAN override the `block` tags of `abc.html` if my component template
|
||||
uses `extends`. In that case, just as you would expect, the `block inner` inside
|
||||
`abc.html` will render `OVERRIDEN`:
|
||||
|
||||
````py
|
||||
@component.register("my_comp")
|
||||
class MyComp(component.Component):
|
||||
template_name = """
|
||||
{% extends "abc.html" %}
|
||||
|
||||
{% block inner %}
|
||||
OVERRIDEN
|
||||
{% endblock %}
|
||||
"""
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
4. This is where it gets interesting (but still intuitive). You can insert even
|
||||
new `slots` inside these "overriding" blocks:
|
||||
|
||||
```py
|
||||
@component.register("my_comp")
|
||||
class MyComp(component.Component):
|
||||
template_name = """
|
||||
{% extends "abc.html" %}
|
||||
|
||||
{% load component_tags %}
|
||||
{% block "inner" %}
|
||||
OVERRIDEN
|
||||
{% slot "new_slot" %}
|
||||
hello
|
||||
{% endslot %}
|
||||
{% endblock %}
|
||||
"""
|
||||
```
|
||||
|
||||
And you can then pass fill for this `new_slot` when rendering the component:
|
||||
|
||||
```django
|
||||
{% component "my_comp" %}
|
||||
{% fill "new_slot" %}
|
||||
XYZ
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
NOTE: Currently you can supply fills for both `new_slot` and `body` slots, and you will
|
||||
not get an error for an invalid/unknown slot name. But since `body` slot is not rendered,
|
||||
it just won't do anything. So this renders the same as above:
|
||||
|
||||
```django
|
||||
{% component "my_comp" %}
|
||||
{% fill "new_slot" %}
|
||||
XYZ
|
||||
{% endfill %}
|
||||
{% fill "body" %}
|
||||
www
|
||||
{% endfill %}
|
||||
{% endcomponent %}
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue