mirror of
https://github.com/django-components/django-components.git
synced 2025-08-31 11:17:21 +00:00
docs: fix links in README and "overview" section, add tutorial (#842)
This commit is contained in:
parent
6813c9d7aa
commit
789f3807aa
27 changed files with 1213 additions and 216 deletions
223
docs/guides/devguides/dependency_mgmt.md
Normal file
223
docs/guides/devguides/dependency_mgmt.md
Normal file
|
@ -0,0 +1,223 @@
|
|||
# JS and CSS rendering
|
||||
|
||||
Aim of this doc is to share the intuition on how we manage the JS and CSS ("dependencies")
|
||||
associated with components, and how we render them.
|
||||
|
||||
## Starting conditions
|
||||
|
||||
1. First of all, when we consider a component, it has two kind of dependencies - the "inlined" JS and CSS, and additional linked JS and CSS via `Media.js/css`:
|
||||
|
||||
```py
|
||||
from django_components import Component, types
|
||||
|
||||
class MyTable(Component):
|
||||
# Inlined JS
|
||||
js: types.js = """
|
||||
console.log(123);
|
||||
"""
|
||||
|
||||
# Inlined CSS
|
||||
css: types.css = """
|
||||
.my-table {
|
||||
color: red;
|
||||
}
|
||||
"""
|
||||
|
||||
# Linked JS / CSS
|
||||
class Media:
|
||||
js = [
|
||||
"script-one.js", # STATIC file relative to component file
|
||||
"/script-two.js", # URL path
|
||||
"https://example.com/script-three.js", # URL
|
||||
]
|
||||
|
||||
css = [
|
||||
"style-one.css", # STATIC file relative to component file
|
||||
"/style-two.css", # URL path
|
||||
"https://example.com/style-three.css", # URL
|
||||
]
|
||||
```
|
||||
|
||||
2. Second thing to keep in mind is that all component's are eventually rendered into a string. And so, if we want to associate extra info with a rendered component, it has to be serialized to a string.
|
||||
|
||||
This is because a component may be embedded in a Django Template with the `{% component %}` tag, which, when rendered, is turned into a string:
|
||||
|
||||
```py
|
||||
template = Template("""
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% component "my_table" / %}
|
||||
</div>
|
||||
""")
|
||||
|
||||
html_str = template.render(Context({}))
|
||||
```
|
||||
|
||||
And for this reason, we take the same approach also when we render a component with `Component.render()` - It returns a string.
|
||||
|
||||
3. Thirdly, we also want to add support for JS / CSS variables. That is, that a variable defined on the component would be somehow accessible from within the JS script / CSS style.
|
||||
|
||||
A simple approach to this would be to modify the inlined JS / CSS directly, and insert them for each component. But if you had extremely large JS / CSS, and e.g. only a single JS / CSS variable that you want to insert, it would be extremely wasteful to copy-paste the JS / CSS for each component instance.
|
||||
|
||||
So instead, a preferred approach here is to defined and insert the inlined JS / CSS only once, and have some kind of mechanism on how we make correct the JS / CSS variables available only to the correct components.
|
||||
|
||||
4. Last important thing is that we want the JS / CSS dependencies to work also with HTML fragments.
|
||||
|
||||
So normally, e.g. when a user hits URL of a web page, the server renders full HTML document, with `<!doctype>`, `<html>`, `<head>`, and `<body>`. In such case, we know about ALL JS and CSS dependencies at render time, so we can e.g. insert them into `<head>` and `<body>` ourselves.
|
||||
|
||||
However this renders only the initial state. HTML fragments is a common pattern where interactivity is added to the web page by fetching and replacing bits of HTML on the main HTML document after some user action.
|
||||
|
||||
In the case of HTML fragments, the HTML is NOT a proper document, but only the HTML that will be inserted somewhere into the DOM.
|
||||
|
||||
The challenge here is that Django template for the HTML fragment MAY contain components, and these components MAY have inlined or linked JS and CSS.
|
||||
|
||||
```py
|
||||
def fragment_view(request):
|
||||
template = Template("""
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% component "my_table" / %}
|
||||
</div>
|
||||
""")
|
||||
|
||||
fragment_str = template.render(Context({}))
|
||||
return HttpResponse(fragment_str, status=200)
|
||||
```
|
||||
|
||||
User may use different libraries to fetch and insert the HTML fragments (e.g. HTMX, AlpineJS, ...). From our perspective, the only thing that we can reliably say is that we expect that the HTML fragment WILL be eventually inserted into the DOM.
|
||||
|
||||
So to include the corresponding JS and CSS, a simple approach could be to append them to the HTML as `<style>` and `<script>`, e.g.:
|
||||
|
||||
```html
|
||||
<!-- Original content -->
|
||||
<div>...</div>
|
||||
<!-- Associated CSS files -->
|
||||
<link href="http://..." />
|
||||
<style>
|
||||
.my-class {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
<!-- Associated JS files -->
|
||||
<script src="http://..."></script>
|
||||
<script>
|
||||
console.log(123);
|
||||
</script>
|
||||
```
|
||||
|
||||
But this has a number of issues:
|
||||
|
||||
- The JS scripts would run for each instance of the component.
|
||||
- Bloating of the HTML file, as each inlined JS or CSS would be included fully for each component.
|
||||
- While this sound OK, this could really bloat the HTML files if we used a UI component library for the basic building blocks like buttons, lists, cards, etc.
|
||||
|
||||
## Flow
|
||||
|
||||
So the solution should address all the points above. To achieve that, we manage the JS / CSS dependencies ourselves in the browser. So when a full HTML document is loaded, we keep track of which JS and CSS have been loaded. And when an HTML fragment is inserted, we check which JS / CSS dependencies it has, and load only those that have NOT been loaded yet.
|
||||
|
||||
This is how we achieve that:
|
||||
|
||||
1. When a component is rendered, it inserts an HTML comment containing metadata about the rendered component.
|
||||
|
||||
So a template like this
|
||||
|
||||
```django
|
||||
{% load component_tags %}
|
||||
<div>
|
||||
{% component "my_table" / %}
|
||||
</div>
|
||||
{% component "button" %}
|
||||
Click me!
|
||||
{% endcomponent %}
|
||||
```
|
||||
|
||||
May actually render:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<!-- _RENDERED "my_table_10bc2c,c020ad" -->
|
||||
<table>
|
||||
...
|
||||
</table>
|
||||
</div>
|
||||
<!-- _RENDERED "button_309dcf,31c0da" -->
|
||||
<button>Click me!</button>
|
||||
```
|
||||
|
||||
Each `<!-- _RENDERED -->` comment includes comma-separated data - a unique hash for the component class, e.g. `my_table_10bc2c`, and the component ID, e.g. `c020ad`.
|
||||
|
||||
This way, we or the user can freely pass the rendered around or transform it, treating it as a string to add / remove / replace bits. As long as the `<!-- _RENDERED -->` comments remain in the rendered string, we will be able to deduce which JS and CSS dependencies the component needs.
|
||||
|
||||
2. Post-process the rendered HTML, extracting the `<!-- _RENDERED -->` comments, and instead inserting the corresponding JS and CSS dependencies.
|
||||
|
||||
If we dealt only with JS, then we could get away with processing the `<!-- _RENDERED -->` comments on the client (browser). However, the CSS needs to be processed still on the server, so the browser receives CSS styles already inserted as `<style>` or `<link>` HTML tags. Because if we do not do that, we get a [flash of unstyled content](https://en.wikipedia.org/wiki/Flash_of_unstyled_content), as there will be a delay between when the HTML page loaded and when the CSS was fetched and loaded.
|
||||
|
||||
So, assuming that a user has already rendered their template, which still contains `<!-- _RENDERED -->` comments, we need to extract and process these comments.
|
||||
|
||||
There's multiple ways to achieve this:
|
||||
|
||||
- The approach recommended to the users is to use the `ComponentDependencyMiddleware` middleware, which scans all outgoing HTML, and post-processes the `<!-- _RENDERED -->` comments.
|
||||
|
||||
- If users are using `Component.render()` or `Component.render_to_response()`, these post-process the `<!-- _RENDERED -->` comments by default.
|
||||
|
||||
- NOTE: Users are able to opt out of the post-processing by setting `render_dependencies=False`.
|
||||
|
||||
- For advanced use cases, users may use `render_dependencies()` directly. This is the function that both `ComponentDependencyMiddleware` and `Component.render()` call internally.
|
||||
|
||||
`render_dependencies()`, whether called directly, via middleware or other way, does the following:
|
||||
|
||||
1. Find all `<!-- _RENDERED -->` comments, and for each comment:
|
||||
|
||||
2. Look up the corresponding component class.
|
||||
|
||||
3. Get the component's inlined JS / CSS from `Component.js/css`, and linked JS / CSS from `Component.Media.js/css`.
|
||||
|
||||
4. Generate JS script that loads the JS / CSS dependencies.
|
||||
|
||||
5. Insert the JS scripts either at the end of `<body>`, or in place of `{% component_dependencies %}` / `{% component_js_dependencies %}` tags.
|
||||
|
||||
6. To avoid the [flash of unstyled content](https://en.wikipedia.org/wiki/Flash_of_unstyled_content), we need place the styles into the HTML instead of dynamically loading them from within a JS script. The CSS is placed either at the end of `<head>`, or in place of `{% component_dependencies %}` / `{% component_css_dependencies %}`
|
||||
|
||||
7. We cache the component's inlined JS and CSS, so they can be fetched via an URL, so the inlined JS / CSS an be treated the same way as the JS / CSS dependencies set in `Component.Media.js/css`.
|
||||
- NOTE: While this is currently not entirely necessary, it opens up the doors for allowing plugins to post-process the inlined JS and CSS. Because after it has been post-processed, we need to store it somewhere.
|
||||
|
||||
3. Server returns the post-processed HTML.
|
||||
|
||||
4. In the browser, the generated JS script from step 2.4 is executed. It goes through all JS and CSS dependencies it was given. If some JS / CSS was already loaded, it is NOT fetched again. Otherwise it generates the corresponding `<script>` or `<link>` HTML tags to load the JS / CSS dependencies.
|
||||
|
||||
In the browser, the "dependency manager JS" may look like this:
|
||||
|
||||
```js
|
||||
// Load JS or CSS script if not loaded already
|
||||
Components.loadJs('<script src="/abc/xyz/script.js">');
|
||||
Components.loadCss('<link href="/abc/xyz/style.css">');
|
||||
|
||||
// Or mark one as already-loaded, so it is ignored when
|
||||
// we call `loadJs`
|
||||
Components.markScriptLoaded("js", "/abc/def");
|
||||
```
|
||||
|
||||
Note that `loadJs() / loadCss()` receive whole `<script> / <link>` tags, not just the URL.
|
||||
This is because when Django's `Media` class renders JS and CSS, it formats it as `<script>` and `<link>` tags.
|
||||
And we allow users to modify how the JS and CSS should be rendered into the `<script>` and `<link>` tags.
|
||||
|
||||
So, if users decided to add an extra attribute to their `<script>` tags, e.g. `<script defer src="http://..."></script>`,
|
||||
then this way we make sure that the `defer` attribute will be present on the `<script>` tag when
|
||||
it is inserted into the DOM at the time of loading the JS script.
|
||||
|
||||
5. To be able to fetch component's inlined JS and CSS, django-components adds a URL path under:
|
||||
|
||||
`/components/cache/<str:comp_cls_hash>.<str:script_type>/`
|
||||
|
||||
E.g. `/components/cache/my_table_10bc2c.js/`
|
||||
|
||||
This endpoint takes the component's unique hash, e.g. `my_table_10bc2c`, and looks up the component's inlined JS or CSS.
|
||||
|
||||
---
|
||||
|
||||
Thus, with this approach, we ensure that:
|
||||
|
||||
1. All JS / CSS dependencies are loaded / executed only once.
|
||||
2. The approach is compatible with HTML fragments
|
||||
3. The approach is compatible with JS / CSS variables.
|
||||
4. Inlined JS / CSS may be post-processed by plugins
|
253
docs/guides/devguides/slot_rendering.md
Normal file
253
docs/guides/devguides/slot_rendering.md
Normal file
|
@ -0,0 +1,253 @@
|
|||
# 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 %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
6. The names of the slots and fills may be defined using variables
|
||||
```django
|
||||
| -- {% slot slot_name %} ---
|
||||
| ---- STU {{ my_var }}
|
||||
| -- {% endslot %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
7. The slot and fill names may be defined using for loops or other variables defined within the template (e.g. `{% with %}` tag or `{% ... as var %}` syntax)
|
||||
```django
|
||||
| -- {% for slot_name in slots %} ---
|
||||
| ---- {% slot slot_name %} ---
|
||||
| ------ STU {{ slot_name }}
|
||||
| ---- {% endslot %} ---
|
||||
| -- {% endfor %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
8. Variables for names and for loops allow us implement "passthrough slots" - that is, taking all slots that our component received, and passing them to a child component, dynamically.
|
||||
```django
|
||||
| -- {% component "mycomp" %} ---
|
||||
| ---- {% for slot_name in slots %} ---
|
||||
| ------ {% fill slot_name %} ---
|
||||
| -------- {% slot slot_name %} ---
|
||||
| ---------- XYZ {{ slot_name }}
|
||||
| --------- {% endslot %}
|
||||
| ------- {% endfill %}
|
||||
| ---- {% endfor %} ---
|
||||
| -- {% endcomponent %} ---
|
||||
| ----
|
||||
```
|
||||
|
||||
9. Putting that all together, a document may look like this:
|
||||
```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 %} ---
|
||||
| -------
|
||||
| -- {% for slot_name in slots %} ---
|
||||
| ---- {% component "mycomp" %} ---
|
||||
| ------- {% slot slot_name %}
|
||||
| ---------- STU {{ slot_name }}
|
||||
| ------- {% endslot %}
|
||||
| ---- {% endcomponent %} ---
|
||||
| -- {% endfor %} ---
|
||||
| ----
|
||||
| -- {% component "mycomp" %} ---
|
||||
| ---- {% for slot_name in slots %} ---
|
||||
| ------ {% fill slot_name %} ---
|
||||
| -------- {% slot slot_name %} ---
|
||||
| ---------- XYZ {{ slot_name }}
|
||||
| --------- {% endslot %}
|
||||
| ------- {% endfill %}
|
||||
| ---- {% endfor %} ---
|
||||
| -- {% endcomponent %} ---
|
||||
| -------
|
||||
```
|
||||
|
||||
10. Given the above, we want to render the slots with `{% fill %}` tag that were defined OUTSIDE of this template. How do I do that?
|
||||
|
||||
> _NOTE: Before v0.110, slots were resolved statically, by walking down the Django Template and Nodes. However, this did not allow for using for loops or other variables defined in the template._
|
||||
|
||||
Currently, this consists of 2 steps:
|
||||
|
||||
1. If a component is rendered within a template using `{% component %}` tag, determine the given `{% fill %}` tags in the component's body (the content in between `{% component %}` and `{% endcomponent %}`).
|
||||
|
||||
After this step, we know about all the fills that were passed to the component.
|
||||
|
||||
2. Then we simply render the template as usual. And then we reach the `{% slot %}` tag, we search the context for the available fills.
|
||||
|
||||
- If there IS a fill with the same name as the slot, we render the fill.
|
||||
- If the slot is marked `default`, and there is a fill named `default`, then we render that.
|
||||
- Otherwise, we render the slot's default content.
|
||||
|
||||
11. Obtaining the fills from `{% fill %}`.
|
||||
|
||||
When a component is rendered with `{% component %}` tag, and it has some content in between `{% component %}` and `{% endcomponent %}`, we want to figure out if that content is a default slot (no `{% fill %}` used), or if there is a collection of named `{% fill %}` tags:
|
||||
|
||||
Default slot:
|
||||
|
||||
```django
|
||||
| -- {% component "mycomp" %} ---
|
||||
| ---- STU {{ slot_name }}
|
||||
| -- {% endcomponent %} ---
|
||||
```
|
||||
|
||||
Named slots:
|
||||
|
||||
```django
|
||||
| -- {% component "mycomp" %} ---
|
||||
| ---- {% fill "slot_a" %}
|
||||
| ------ STU
|
||||
| ---- {% endslot %}
|
||||
| ---- {% fill "slot_b" %}
|
||||
| ------ XYZ
|
||||
| ---- {% endslot %}
|
||||
| -- {% endcomponent %} ---
|
||||
```
|
||||
|
||||
To respect any forloops or other variables defined within the template to which the fills may have access,
|
||||
we:
|
||||
|
||||
1. Render the content between `{% component %}` and `{% endcomponent %}` using the context
|
||||
outside of the component.
|
||||
2. When we reach a `{% fill %}` tag, we capture any variables that were created between
|
||||
the `{% component %}` and `{% fill %}` tags.
|
||||
3. When we reach `{% fill %}` tag, we do not continue rendering deeper. Instead we
|
||||
make a record that we found the fill tag with given name, kwargs, etc.
|
||||
4. After the rendering is done, we check if we've encountered any fills.
|
||||
If yes, we expect only named fills. If no, we assume that the the component's body
|
||||
is a default slot.
|
||||
5. Lastly we process the found fills, and make them available to the context, so any
|
||||
slots inside the component may access these fills.
|
||||
|
||||
12. Rendering slots
|
||||
|
||||
Slot rendering works similarly to collecting fills, in a sense that we do not search
|
||||
for the slots ahead of the time, but instead let Django handle the rendering of the template,
|
||||
and we step in only when Django come across as `{% slot %}` tag.
|
||||
|
||||
When we reach a slot tag, we search the context for the available fills.
|
||||
|
||||
- If there IS a fill with the same name as the slot, we render the fill.
|
||||
- If the slot is marked `default`, and there is a fill named `default`, then we render that.
|
||||
- Otherwise, we render the slot's default content.
|
||||
|
||||
## 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()`.
|
168
docs/guides/devguides/slots_and_blocks.md
Normal file
168
docs/guides/devguides/slots_and_blocks.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
# 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
|
||||
from django_components import Component, register
|
||||
|
||||
@register("my_comp_extends")
|
||||
class MyCompWithExtends(Component):
|
||||
template = """{% extends "abc.html" %}"""
|
||||
|
||||
@register("my_comp_include")
|
||||
class MyCompWithInclude(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
|
||||
@register("my_comp")
|
||||
class MyComp(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
|
||||
@register("my_comp")
|
||||
class MyComp(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
|
||||
@register("my_comp")
|
||||
class MyComp(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