-
-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scoped slots - Passing data to slots and accessing them in fills #494
Comments
Why are we not passing all slot variables to fills automatically? I don't think stopping this is what context_behavior="isolated" means, just like isolating the slot from it's component isn't what you expect? Thinking about it, that might lead to params shadowing, so maybe opting in to it in the fill tag still makes sense. Maybe that should also be the way to access the default data, instead of the unergonomic "as variable" we have now. {% slot "content" %}
Input: {{ input }}
{% endslot %} {% component "card" %}
{% slot "content" slot_data="slot_data" %}
Input: {{ slot_data.input }}
{% endslot %}
{% endcomponent %} No strong opinions here, just thinking out loud. |
@EmilStenstrom could you elaborate what you mean by "default data"? Do you mean variables referenced inside the Yeah, shadowing is one problem. Secondly, from the perspective of a component library author, the variables I expose via the slot are my public API, so I want to have a control over that. So automatically exposing variables inside the slot tag may not be desirable, as I may want to expose additional variables, or exclude some. NOTE: I also updated the original description. Accidentally, when I was writing it, I was thinking that |
@JuroOravec "default data" = The default slot content that you can access via "as variable". I'm saying that maybe About shadowing, this solves this well: {% slot "content" slot_data="slot_data" %} That leaves letting people only expose some parts of the slot data to fill templates. Are you sure this is really necessary? Exposing nothing by default, and then everything with slot_data should be a very large part of all use-cases, don't you think? |
@EmilStenstrom Interesting with the
Also, and this is related to the topic below, but whichever option we choose, the way I think about it, I think that the
Just as a reminder to myself, this all is relevant only for But hard to say, as it very much depends on the component design. From my experience with Vue/Vuetify, accessing the slot's data was common for UI library components that were more complicated like tables (e.g. to customize how table rows are rendered, for which one needed to access the cell value), but for simpler components like layouts (e.g. card), buttons or forms fields, this was not that common. For business logic components, I'd say maybe 30-50% of the time. But again, other people might do things differently. So, in this case, my hunch would be to go with an explicit API, so it's clear which variable comes from the slot, and which from |
One more idea for accessing So this way we wouldn't need to define the {% fill "my_slot" %}
{{ component_vars.slot_data.abc }}
{% endfill %} |
About default template content: I like the first version with default_slot, think it fits nicely with slot_data. No need to use component_vars at all. About the second point: My suggestion is that we don't change the slot tag, and instead just control it with |
Hmm... the |
I don't have any strong preference between the two. @dylanjcastillo what do you think? To recap, the {% fill "my_slot" default_slot="default_slot" %}
{{ default_slot }}
{% endfill %} The {% fill "my_slot" %}
{{ component_vars.default_slot }}
{% endfill %} Actually, implementation wise, I'm slightly more in favour of
Yeah, that's what I meant too, if I understand correctly. But correct me if I misunderstood:
|
In you original suggestion you had {% slot "my_slot" default %}{{ input }}{% endslot %} context_behaviour="django" {% fill "my_slot" %}{{ input }}{% endfill %} <-- Works! context_behaviour="isolated" {% fill "my_slot" %}Input not accessible{% endfill %} <-- Error!
context_behaviour="isolated" {% fill "my_slot" slot_data="slot_data" %}{{ slot_data.input }}{% endfill %} <-- Works! |
Right, I understand now. That's where I as a component author would prefer to specify the variables passed to slot tag explicitly. Otherwise, if I have a public package that manages e.g. 20+ components, I'd be harder to manage, and I would need to come up with custom workaround like prefixing private vars with underscore, to distinguish between private and public vars. |
@JuroOravec Could you give a concrete example where it's hard to manage? Maybe if you have an existing component that would benefit from this? I just feel that we're adding so much extra work for people: First you have to specify what variables to expose, then you have to specify the variable they land in. This is for every slot, in every component you have, for everyone that uses the library (isolated is default now). If you do have a library with 20+ components as a component library author (are there django-component libraries?) you only have to care about the public API for all your slots, but this is still a lot of extra work. |
I wonder if we should switch back to "django" as the default context_behavior, and have isolated be a mode the we reserve for everyone that want the kind of detailed scope control you are after? I think that might be the best path here, that both gives you the control you want, but does not complicate things for beginners? |
To give you an idea what I'm talking about. So far I have 10 "generic" material design components (below), and I'd like to grow that collection. None of the components uses "scoped slots" (slot data) as of now, because I haven't implemented that in my "fork". So I also don't feel too concerned about people having to write too much - people can design slots also without the "scoped slots" feature (just differently, and with maybe a bit less flexibility). But to give you an idea what kind of data could be made available through the slots, I link to corresponding Vuetify components. In the links, slots with For each component, I also include how often I feel I had to access the "slot data" when I worked with it in Vue/Vuetify:
Note: You see that it feels really variable. As per this:
The way I see it is like functions - sure, you don't need to annotate the input / output types, and just use With slots it's similar - we don't have to explicitly define the exposed variables, but then someone else might come across the file, and be like "hmm, these variables aren't used anywhere in this file, I might as well delete them". Consider the example below, where we'd implicitly pass the context to slots. Can you tell which one of @component.register("my_comp")
class MyComp(component.Component):
template = """
<div>
{{ abc }}
...
{% slot "slot_123" %} {% endslot %}
...
{% slot "slot_456" %} {% endslot %}
</div>
"""
def get_context_data(self):
return {
"abc": 1,
"def": 2,
"xyz": 3,
} And same applies to accessing the data in fills. If I don't explicitly define where my variables are coming from, then I won't be able to tell if, inside a It's the same kind of issue with Django components before we've added the "isolated" mode. The isolation and explicit interfaces means you can safely work on one component, knowing that you will not accidentally break other parts of the system. So IMO there's 3 aspects to this:
I believe this feature should be explicit and isolated (implicit and non-isolated is what you get with the vanilla "django" behavior). Low verbosity is nice, but that's a secondary aspect. To achieve the explicitness and isolation-ness, there is some minimal amount of information that needs to be provided - So IMO defining the data for each slot (or not if none given), and declaring whether you're accessing slot data from within a fill (or not), that's required. Where I think there is a leeway is how that info is declared. E.g. what in django-components could be achieved as: {% fill "my_slot" slot_data="data" %}
{{ data.message }}
{% endfill %} Then in Vue they squeezed the fill name and slot data access into one: <template #default="{ message }">
{{ message }}
</template> |
Your call |
I see what you mean, great example with having an existing component context dict, and wondering if it's used anywhere. I think we should go with your suggestion and be very specific when in isolated mode, but at the same time switch back to "django" as the default. I fear that the kinds of issues you bring up are for later in a project lifetime, are great for maintainability, but might hinder some people that are getting started and don't (yet) need strict isolation. They can always add it later on. Does this make sense? |
Yup, makes sense. While I can't relate to newcomer Django user who's new to frontend templating (because I got to Django already being familiar with the concepts), there's plenty of people who are trying to wrap their heads around Vue's scoped slots, so I get what you're saying. |
So, new plan: context_behaviour="django"{% slot "my_slot" default %}{{ input }}{% endslot %} {% fill "my_slot" %}{{ input }}{% endfill %} <-- Works! context_behaviour="isolated"{% slot "my_slot" default %}{{ input }}{% endslot %} <-- Exposes no variables to fill tags {% slot "my_slot" default input=input %}{{ input }}{% endslot %} <-- Exposes input variable to fill tags {% fill "my_slot" %}{{ input }}{% endfill %} <-- Error, doesn't expose slot variables to template {% fill "my_slot" slot_data="slot_data" %}{{ slot_data.input }}{% endfill %} <-- Works! Does this match your idea of how this works? |
Yup, that's what I had in mind! |
@EmilStenstrom I've updated the MR to match the API in your latest comment, so the MR is now ready for review. 🎉 I also made an issue for changing the context_behavior default, so we tackle that separately - #498 |
This has been released in https://github.com/EmilStenstrom/django-components/releases/tag/0.76 |
After #491 I wanted to do a small one now.
Another feature that I'm missing as a component author is what Vue calls "scoped slots". Basically, it means that you can pass data to slots and then access them in fills.
Why is it useful?
Consider a component with slot(s). This component may do some processing on the inputs, and then use the processed variable in the slot's default template:
Currently, if I decide to override the "content" slot, then I cannot make use of the
input
variable (if I'm using theisolated
option forcontext_behavior
). I would either have to recomputeinput
myself, or pass the slot content as a function to MyComp. Recomputing is not ideal if the computation is expensive. And passing slot fills as functions means we cannot separate Python and HTML logic.Vue's approach is that instead, the component with the slot can decide to pass some variables to the slot. And these variables can then be optionally used by the slot fill:
This has also been implemented in django-web-component (DWC). DWC allows to pass only one variable to the slot. The name under which it is then accessed in the slot fill is defined by
:let
. Also notice that the example below uses a default slot, so:let
is defined on the component itself.Implementation notes:
API for passing data to slots - I'm thinking about two approaches:
All extra slot kwargs are passed through:
would collect
key1=value1 key2="value2"
into a dictionary and pass that dictto the fill tag.
The slot data would be defined under a specific keyword, like
data
. And we couldalso use the
prefix:key=val
construct here:Don't have a preference here. Option 2 is more future-proof, as it allows for new inputs to be added to
slot
tag without disruption. But if I look at Vue or DWC, then it seems there's a low chance of additional keys being added.Actually, maybe option 2 is slightly better, because in option 1, we cannot pass kwarg "default", because it would collide with the
default
flag. But in the option 2, we can dodata:default=...
.API for accessing slot data in fills
slot_data
.DWC used the prefixed
:let
because their "slot attributes" feature allows to passdata INTO component via fills, so any kwargs not prefixed with
:
are interpreted to beset as "slot's attributes". Personally I'm not a fan of that design decision, so IMO we don't
need to prefix anything here.
NOTE: I like the idea that we'd use the same keyword on both
slot
andfill
tags, to hint that these are the same thing. Like in the examples above it'sdata
on bothslot
andfill
.NOTE: This should be compatible with other
fill
'sas var
syntax:In the example above, the requirements are that
as "xxx"
needs to be last, andfill "yyy"
needs to be first.The text was updated successfully, but these errors were encountered: