Skip to content

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")
  • 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 pass render_dependencies=False to 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() and get_component_files() to get the same list of dirs / files that would be imported by autodiscover(), 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_dependencies

  • The ComponentRegistry settings RegistrySettings were lowercased to align with the global settings:

  • RegistrySettings.CONTEXT_BEHAVIOR -> RegistrySettings.context_behavior
  • RegistrySettings.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_change was renamed to reload_on_file_change. And now it properly triggers server reload when any file in the component dirs change. The old name reload_on_template_change is deprecated and will be removed in v1.

  • The setting forbidden_static_files was renamed to static_files_forbidden to align with static_files_allowed The old name forbidden_static_files is 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 preload of {% 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 name kwarg 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 as Component.input.slots["default"], e.g.:
class MyTable(Component):
    def get_context_data(self, *args, **kwargs):
        default_slot = self.input.slots["default"]
        ...
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 via self.input.slots in get_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 default and required flags 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.slots can 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_filled variable (and the {{ component_vars.is_filled }} context variable) now returns False when 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 %}