v0.110 π¨π’ (2024-11-25)
β οΈ Attention β οΈ - Please update to v0.117 to fix known bugs. See #791 and #789 and #818.
General
BREAKING CHANGES π¨π’
Installation changes:
- If your components include JS or CSS, you now must use the middleware and add django-components' URLs to your
urlpatterns(See "Adding support for JS and CSS")
- If your components include JS or CSS, you now must use the middleware and add django-components' URLs to your
Component typing signature changed from
Component[Args, Kwargs, Data, Slots]to
Component[Args, Kwargs, Slots, Data, JsData, CssData]If you rendered a component A with
Component.render()and then inserted that into another component B, now you must passrender_dependencies=Falseto component A:prerendered_a = CompA.render( args=[...], kwargs={...}, render_dependencies=False, ) html = CompB.render( kwargs={ content=prerendered_a, }, )
Feat
- Intellisense and mypy validation for settings:
Instead of defining the COMPONENTS settings as a plain dict, you can use ComponentsSettings:
# settings.py
from django_components import ComponentsSettings
COMPONENTS = ComponentsSettings(
autodiscover=True,
...
)
- Use
get_component_dirs()andget_component_files()to get the same list of dirs / files that would be imported byautodiscover(), but without actually importing them.
Refactor
For advanced use cases, use can omit the middleware and instead manage component JS and CSS dependencies yourself with
render_dependenciesThe
ComponentRegistrysettingsRegistrySettingswere lowercased to align with the global settings:RegistrySettings.CONTEXT_BEHAVIOR->RegistrySettings.context_behaviorRegistrySettings.TAG_FORMATTER->RegistrySettings.tag_formatter
The old uppercase settings CONTEXT_BEHAVIOR and TAG_FORMATTER are deprecated and will be removed in v1.
The setting
reload_on_template_changewas renamed toreload_on_file_change. And now it properly triggers server reload when any file in the component dirs change. The old namereload_on_template_changeis deprecated and will be removed in v1.The setting
forbidden_static_fileswas renamed tostatic_files_forbiddento align withstatic_files_allowedThe old nameforbidden_static_filesis deprecated and will be removed in v1.
Tags
BREAKING CHANGES π¨π’
{% component_dependencies %}tag was removed. Instead, use{% component_js_dependencies %}and{% component_css_dependencies %}The combined tag was removed to encourage the best practice of putting JS scripts at the end of
<body>, and CSS styles inside<head>.On the other hand, co-locating JS script and CSS styles can lead to a flash of unstyled content, as either JS scripts will block the rendering, or CSS will load too late.
The undocumented keyword arg
preloadof{% component_js_dependencies %}and{% component_css_dependencies %}tags was removed. This will be replaced with HTML fragment support.
Fix
- Allow using forward slash (
/) when defining custom TagFormatter, e.g.{% MyComp %}..{% /MyComp %}.
Refactor
{% component_dependencies %}tags are now OPTIONAL - If your components use JS and CSS, but you don't use{% component_dependencies %}tags, the JS and CSS will now be, by default, inserted at the end of<body>and at the end of<head>respectively.
Slots
Feat
- Fills can now be defined within loops (
{% for %}) or other tags (like{% with %}), or even other templates using{% include %}.
Following is now possible
{% component "table" %}
{% for slot_name in slots %}
{% fill name=slot_name %}
{% endfill %}
{% endfor %}
{% endcomponent %}
- If you need to access the data or the default content of a default fill, you can set the
namekwarg to"default".
Previously, a default fill would be defined simply by omitting the {% fill %} tags:
{% component "child" %}
Hello world
{% endcomponent %}
But in that case you could not access the slot data or the default content, like it's possible for named fills:
{% component "child" %}
{% fill name="header" data="data" %}
Hello {{ data.user.name }}
{% endfill %}
{% endcomponent %}
Now, you can specify default tag by using name="default":
{% component "child" %}
{% fill name="default" data="data" %}
Hello {{ data.user.name }}
{% endfill %}
{% endcomponent %}
- When inside
get_context_data()or other component methods, the default fill can now be accessed asComponent.input.slots["default"], e.g.:
class MyTable(Component):
def get_context_data(self, *args, **kwargs):
default_slot = self.input.slots["default"]
...
- You can now dynamically pass all slots to a child component. This is similar to passing all slots in Vue:
class MyTable(Component):
def get_context_data(self, *args, **kwargs):
return {
"slots": self.input.slots,
}
template: """
<div>
{% component "child" %}
{% for slot_name in slots %}
{% fill name=slot_name data="data" %}
{% slot name=slot_name ...data / %}
{% endfill %}
{% endfor %}
{% endcomponent %}
</div>
"""
Fix
Slots defined with
{% fill %}tags are now properly accessible viaself.input.slotsinget_context_data()Do not raise error if multiple slots with same name are flagged as default
Slots can now be defined within loops (
{% for %}) or other tags (like{% with %}), or even other templates using{% include %}.
Previously, following would cause the kwarg name to be an empty string:
{% for slot_name in slots %}
{% slot name=slot_name %}
{% endfor %}
Refactor
- When you define multiple slots with the same name inside a template, you now have to set the
defaultandrequiredflags individually.
<div class="calendar-component">
<div class="header">
{% slot "image" default required %}Image here{% endslot %}
</div>
<div class="body">
{% slot "image" default required %}Image here{% endslot %}
</div>
</div>
This means you can also have multiple slots with the same name but different conditions.
E.g. in this example, we have a component that renders a user avatar - a small circular image with a profile picture of name initials.
If the component is given image_src or name_initials variables, the image slot is optional. But if neither of those are provided, you MUST fill the image slot.
<div class="avatar">
{% if image_src %}
{% slot "image" default %}
<img src="{{ image_src }}" />
{% endslot %}
{% elif name_initials %}
{% slot "image" default required %}
<div style="
border-radius: 25px;
width: 50px;
height: 50px;
background: blue;
">
{{ name_initials }}
</div>
{% endslot %}
{% else %}
{% slot "image" default required / %}
{% endif %}
</div>
- The slot fills that were passed to a component and which can be accessed as
Component.input.slotscan now be passed through the Django template, e.g. as inputs to other tags.
Internally, django-components handles slot fills as functions.
Previously, if you tried to pass a slot fill within a template, Django would try to call it as a function.
Now, something like this is possible:
class MyTable(Component):
def get_context_data(self, *args, **kwargs):
return {
"child_slot": self.input.slots["child_slot"],
}
template: """
<div>
{% component "child" content=child_slot / %}
</div>
"""
NOTE: Using {% slot %} and {% fill %} tags is still the preferred method, but the approach above may be necessary in some complex or edge cases.
- The
is_filledvariable (and the{{ component_vars.is_filled }}context variable) now returnsFalsewhen you try to access a slot name which has not been defined:
Before:
{{ component_vars.is_filled.header }} -> True
{{ component_vars.is_filled.footer }} -> False
{{ component_vars.is_filled.nonexist }} -> "" (empty string)
After:
{{ component_vars.is_filled.header }} -> True
{{ component_vars.is_filled.footer }} -> False
{{ component_vars.is_filled.nonexist }} -> False
Components no longer raise an error if there are extra slot fills
Components will raise error when a slot is doubly-filled.
E.g. if we have a component with a default slot:
{% slot name="content" default / %}
Now there is two ways how we can target this slot: Either using name="default" or name="content".
In case you specify BOTH, the component will raise an error:
{% component "child" %}
{% fill slot="default" %}
Hello from default slot
{% endfill %}
{% fill slot="content" data="data" %}
Hello from content slot
{% endfill %}
{% endcomponent %}