So, to implement this, I had to do a bit of magic:
Inside tabs, I provided an array
Each tab_item injects said array, and, after the item's content is rendered, it inserts the rendered content into the array.
After all children of tabs are rendered, the tabs content doesn't return it's actual rendered content (which consists only of tab_items. But instead it renders its own HTML, iterating over the array of tab_item contents.
Now, to do that, I had to introduce hooks - optional functions that can be defined to be run specific position of the rendering process.
The hook receives contextual info like the context, and the Template used for rendering, as well as the rendered output (content).
If the hook returns something, we use that something instead of the original output. This way, the hook can override what's returned from the component. If it doesn't return anything, the original output is used.
This is how I used it:
Tabs:
def on_render_after(self, context, template, content):
# By the time we get here, all child TabItem components should have been
# rendered, and they should've populated the tabs list.
tabs: list[TabEntry] = context["tabs"]
return _TabsImpl.render(
kwargs={
"tabs": tabs,
"name": context["name"],
"attrs": context["attrs"],
"header_attrs": context["header_attrs"],
"content_attrs": context["content_attrs"],
},
)
Now, the reason something like this is needed is because it's currently not that easy to intercept the rendering process. One might naively try to override render, but that's just the public API, which would be bypassed when the component is rendered inside a template. One would actually have to intercept Template.render to be able to mimic on_render_before and on_render_after.
So IMO it's better to explicitly offer these hooks.
This relates to the declarative tabs component that I've made and shared in https://github.com/EmilStenstrom/django-components/discussions/540
So, to implement this, I had to do a bit of magic:
tabs
, I provided an arraytab_item
injects said array, and, after the item's content is rendered, it inserts the rendered content into the array.tabs
are rendered, thetabs
content doesn't return it's actual rendered content (which consists only oftab_items
. But instead it renders its own HTML, iterating over the array oftab_item
contents.Now, to do that, I had to introduce hooks - optional functions that can be defined to be run specific position of the rendering process.
Practically, it could be done with one:
The hook receives contextual info like the context, and the Template used for rendering, as well as the rendered output (content).
If the hook returns something, we use that something instead of the original output. This way, the hook can override what's returned from the component. If it doesn't return anything, the original output is used.
This is how I used it:
Tabs:
TabItem:
And for completeness, I would add also
on_render_before
, as:Now, the reason something like this is needed is because it's currently not that easy to intercept the rendering process. One might naively try to override
render
, but that's just the public API, which would be bypassed when the component is rendered inside a template. One would actually have to interceptTemplate.render
to be able to mimicon_render_before
andon_render_after
.So IMO it's better to explicitly offer these hooks.