Xzya / django-web-components

A simple way to create reusable template components in Django.
MIT License
159 stars 4 forks source link

How to use named slots inside the for tag? #10

Closed angelinuxx closed 10 months ago

angelinuxx commented 10 months ago

Hi there! Thank you for this package.

I created a carousel component. Here is my component template

{% load components %}

<div id="{{ attributes.id }}" class="carousel">
  <div class="carousel-container">
    <div class="carousel-wrapper">
      <!-- Slides -->
      {% for slide in slots.slide %}
        <div class="carousel-slide">{% render_slot slide %}</div>
      {% endfor %}
    </div>
  </div>
</div>

When I use the component as in the snippet below it works correctly

{% carousel id="my-carousel" %}
  {% slot slide %}Slide 1{% endslot %}
  {% slot slide %}Slide 2{% endslot %}
  {% slot slide %}Slide 3{% endslot %}
{% endcarousel %}

But if I use the slide slots inside the for tag (as below), nothing is printed

{% carousel id="my-carousel" %}
  {% for item in items %}
    {% slot slide %}{{ item.name }}{% endslot %}
  {% endfor %}
{% endcarousel %}

I found that the content printed inside the for is in inner_block slot and the named slot "slide" is ignored.

Could you please suggest me how to use named slots inside the for tag?

Xzya commented 10 months ago

Hello,

Slots must be placed directly inside the component, they cannot be placed inside other blocks (e.g. for, if). What you can do instead is to split the slide into a separate component:

# components.py

@component.register
def carousel(context):
    return CachedTemplate(
        """
        <div id="{{ attributes.id }}" class="carousel">
            <div class="carousel-container">
                <div class="carousel-wrapper">
                    {% render_slot slots.inner_block %}
                </div>
            </div>
        </div>
        """,
        name="carousel",
    ).render(context)

@component.register
def slide(context):
    return CachedTemplate(
        """
        <div class="carousel-slide">
            {% render_slot slots.inner_block %}
        </div>
        """,
        name="slide",
    ).render(context)

Then you can render the slides in a for loop:

{% carousel id="my-carousel" %}
  {% for item in items %}
    {% slide %}{{ item.name }}{% endslide %}
  {% endfor %}
{% endcarousel %}
angelinuxx commented 10 months ago

Hi @Xzya, thank you!

Xzya commented 10 months ago

Closing since it seems the issue was solved.

For what it's worth, I did look into adding support for defining slots in for loops but I've hit many issues. I tried several solutions but none of them could handle all scenarios and they added a lot of complexity. So for the moment, splitting the slot into separate components and then putting it in a for loop is a much better solution than the alternatives I tried.