askama
askama copied to clipboard
Children like support for macros / included templates
So I did some research and I'd like to open this as a discussion because I haven't been able to find a good solution to this convention.
Problem
Just like in many other templating languages, I'd like to create a wrapper component template that can be re-used with different content inside the template.
This is in some frameworks referred to as <slot/> or children in react.
With Jinja this pattern is possible with the caller functionality with macros: https://jinja.palletsprojects.com/en/3.0.x/templates/#call
Workaround Example
My current solution is to create 2 macros and put the content in between. This can however cause a few problems when used incorrectly.
{% call sc::social_card_start("profile", "Beautiful header content goes here") %}
<span> Here I have total freedom unrelated to the rest of the <code>social_card</code> component </span>
{% call sc::social_card_end() %}
{% macro social_card_start(svg, heading) %}
<section class="border rounded-md bg-pink-200 m-4 p-4">
<header class="flex text-center justify-center items-center gap-2 mb-2">
<svg aria-hidden="true" class="h-7 w-7 fill-blue-950">
<use xlink:href="/svg/icons-sprite.svg#{{svg}}" />
</svg>
<h3 class="text-lg font-medium mb-1">{{heading|safe}}</h3>
</header>
{% endmacro %}
{% macro social_card_end() %}
</section>
</section>
{% endmacro %}
Resolution
I've seen that the support for caller was multiple times declined: #996 #930
This brings me to a question: How should I approach creating and using templates that are meant to wrap around different content?
I think the standard approach would be to use template inheritance? (To be clear, you can have multiple levels of inheritance IIRC.)
(Sorry for the slow response.)
I think the standard approach would be to use template inheritance? (To be clear, you can have multiple levels of inheritance IIRC.)
I have no idea how can this be done with template inheritance.
I am not able to use {% extends %} in a child template. But extending doesn't make sense anyway. I want to display multiple components with the same template.
Maybe it is not clicking to me how can I use this ~but I'd need to define all component templates in the base.html template so they can be used~. Each component must have prefixed block names so they don't overwrite each other when used. Another thing is either I write another template for each usage of the component or I just write a big {% let .... %} bindings prior to importing each time.
This all applies if I understand correctly how this works. Either way, this doesn't help make the code modular. More the other way around.
I guess I don't understand your use case very well. Are you aware, too, that templates implement Display as well, so you can easily nest templates (that is, just pass a value that itself implements Display and render it). Maybe consider a minimal full example of what you're trying to do and why macros/inheritance/includes/nested templates are not sufficient.
I am aware of that. From my workaround example it should be easy to understand that I am just creating a reusable wrapper component. I want to allow parent template to be the one that sends the content into the child template. I want to do this within the templates themselves.
Another example could be this:
{% macro social_card(svg, heading, content) %}
<section class="border rounded-md bg-pink-200 m-4 p-4">
<header class="flex text-center justify-center items-center gap-2 mb-2">
<svg aria-hidden="true" class="h-7 w-7 fill-blue-950">
<use xlink:href="/svg/icons-sprite.svg#{{svg}}" />
</svg>
<h3 class="text-lg font-medium mb-1">{{heading|safe}}</h3>
</header>
{{content}}
</section>
{% endmacro %}
{% call sc::social_card(
"profile",
"Beautiful header content goes here",
"<span> Here I have total freedom unrelated to the rest of the <code>social_card</code> component </span>"
)
I think this is something I would use nested template types for, that seems to map well to "reusable wrapper component"?
I think I'm struggling with a similar issue and Im not sure how to solve it.
I want to create a "Card" component (Similar to https://ui.shadcn.com/docs/components/card), so in the simplest case just a div with some styling that has a child component (and in more complex cases maybe it has multiple blocks).
An example page I want to create is 2 cards next to each other with different children (e.g. one with a table in it and one with some text)
Ive tried a few things:
I tried to use a macro, that works great until I need the child to be more than just text (some html elements). Im not sure if/how I can pass html as an argument to a macro.
I tried to use inheritance. I created a card component with a content block. I could then create components which extended that card (e.g. a text card and a table card) but it wasn't clear how to avoid creating lots of files in this case. e.g. If I have:
// card.html
<div
class="p-6 bg-white rounded-lg shadow-md dark:bg-slate-600 dark:border-gray-700 dark:text-white"
>
{% block content %}{% endblock %}
</div>
I can easily extend this to create a fancy stat card for example:
// stat_card.html
{% extends "card.html" %}
{% block content %}
<div class="w-full">
<div class="mt-3 sm:mt-0 sm:ml-1">{% block value %}{% endblock %}</div>
<h1>{% block title %}{% endblock %}</h1>
</div>
{% endblock %}
But then if I want 3 of these card in index.html how would I add them?
I could create 3 files, one for each card and then include each of those files. Im not a huge fan of that because I need lots of files (each file only exists to define 2 string values to input into the template)
But it would be nice if I could have a syntax like the macro and do
{% call cards::stat_card("foo", 3) %}
{% call cards::stat_card("bar", 2) %}
but it doesn't seem possible to create a macro which contains an extends within it?
If I'm understanding your use-case correctly, you can solve this in Rust rather than in the template itself.
#[derive(Template)]
#[template(text = "<div>{% for child in children %}{{ child }} {% endfor %}</div>, ext = "html")]
struct Container<Child: Template> {
children: Vec<Child>,
}
Though you mention that you want to do this within the templates themselves. Is there a reason that building the structure in Rust is not an option here?