Open JulianFeinauer opened 3 years ago
Stimulus also happens to be the name of the front-end framework where it's entirely possible to build an entire UI without once ever referencing the server past the initial page load, so a tag should not be targeted at the subset functionality of this package while being labeled more generally, as that would be confusing.
I think it actually makes sense to have a stimulus
tag and a reflex
tag. You don't use them on the same element, in most cases, since a Stimulus controller is likely to manage a whole component while Reflexes are fired by individual controls inside that component.
As long as the semantics are right, I love the idea.
What ultimately gets my knickers in a twist is the possibility of doing something like
{% for user in users %}
<section {% stimulus controllers.user %}>
<a href="#" id="profile-link" {% reflex user.profile %}>{{ user.name }}</a>
<img src="#" alt="{{ user.name }}" {% target user.avatar %}>
<a href="#" {% target user.msg %}{% action user.message %}>Tell them how cool they are.</a>
</section>
{% endfor %}
context
so that you can programmatically reason about the details with the full power of Python behind you. It might be graceful from the end-developer perspective to have the template tag accept both dot notation to a dict in the context and a series of arguments, but I personally want to move my controller details out of the templates and into context as much as I can.data-user-id=""
, data-user-friendslist-class=""
) and it's important to be able to dole those out correctly.data-user-target="avatar
syntax, and targets have to be inside a controller. An element can have as many target identifiers as you like, but identifiers for the same controller have to live on the same attribute.data-controller
and a data-reflex
actually has two controllers, one explicit and one implicit. The implicit controller is attached by stimulus_reflex
at page load.data-reflex
is a wrapper around data-action
that does relatively little on the client and runs this.stimulate()
. Some controllers will want to do client-side processing or messaging around the reflex actions, so even in a case where most of the interactivity is going through your server, you don't want to hamstring Stimulus. These methods aren't compatible. When you trigger the data-action
on an element, you have to call this.stimulate()
yourself unless you've also triggered the data-reflex
, and when you trigger the data-reflex
the explicit controller has no way of tracking that reflex because you've gone through a different controller.Stimulus tracks data-attributes with controller-namespaced names (eg. data-user-id="", data-user-friendslist-class="") and it's important to be able to dole those out correctly.
Yes, this is the new stimulus 2.0 syntax. The current docs still contain syntax for stimulus 2.0, see #61
It's an interesting idea using
{% stimulus_controller 'example_reflex' %}
{% endcontroller %}
However controllers can be nested like this.
<div data-reflex="someEvent->ExampleReflex#increment">
<div data-reflex="click->OtherReflex#do_something">
</div>
</div>
From what I can tell the stimulus_controller
won't handle nesting particularly well.
When thinking about this we really have two concepts here that are easy to conflate. We've got stimulus and reflex. They just happen to share some attributes. So it could be wise to separate the two.
So if we have a reflex
template tag that only cares about name
, action
and method
to generate. It would also make sure to validate that we've got a reflex in the backend and that it has the method, if not it would throw an error.
{% reflex name="MyReflex" action="click" method="my_method" %}
data-reflex="click->MyReflex#my_method"
Then we could have a stimulus
tag that could generate all the data other attributes.
One of the slightly annoying bits in stimulus2.0 is that there is syntax that looks like this, the data attributes become fairly verbose.
https://stimulus.hotwire.dev/reference/values
<div data-controller="loader"
data-loader-url-value="/messages">
</div>
https://stimulus.hotwire.dev/reference/targets
<form data-controller="search checkbox">
<input type="checkbox" data-search-target="projects" data-checkbox-target="input">
<input type="checkbox" data-search-target="messages" data-checkbox-target="input">
…
</form>
https://stimulus.hotwire.dev/reference/css-classes
<form data-controller="search"
data-search-loading-class="search--busy"
data-search-no-results-class="search--empty"
>
# Stimulus and reflexes can also access normal data attributes.
# The data attributes don't belong to any particular controller or reflex.
<div data-hello="world" data-hello2="world">
</div>
Personally I'd be happy to limit the current PR for reflex things only to limit the scope. Getting a good solution for the stimulus parts seems to increase the scope quite dramatically.
Thoughts on this @DamnedScholar and @JulianFeinauer?
@jonathan-s I agree with your comment but thats also possible at the moment. In fact, the *_reflex tag can optionally get two list parameters. Then, the first is the controller and the second the method, like that example (from tag_example.html):
<a href="#" {% click_reflex 'example_reflex' 'increment' parameter=parameter %}>click me</a>
If the rexlex is defined INSIDE a {% stimulus 'xx' %}
Block it is allowed to not give a controller (and the one from the stimulus tag is used).
This snippet would be similar:
{% stimulus_controller 'example_reflex' %}
<a href="#" {% click_reflex 'increment' parameter=parameter %}>click me</a>
{% endcontroller %}
Of cours all of this is undocumented but.. just to give you some context. The nice thing would be that we isolate all the technical details away from the user.
@DamnedScholar I added the possibility to pass a dict to the *_reflex functions which contains information about controller, reflex and parameters. You can see this in the test test_tag_by_dict
where a single dict from the context is used for parametrization.
So, when stimulus_reflex
wakes up, it goes through and attaches a new controller to every element with a data-reflex
. Unless I misunderstand something, I don't think there's any chance to convert a declared Reflex into a normal action that triggers your normal controller. The data-reflex
syntax never allows you to specify a controller, while it's explicit inside data-action
.
@DamnedScholar to clarify... I do this in Django and insert the controller on the reflex (as it's needed).
I've been thinking on this for a few days and this an alternative and I think this api for the templatetag could work pretty well.
# The following allows you to add multiple reflexes as a first argument and is shorter.
{% reflex 'click->MyReflex#method' param="param1" param2="param" combined=True permanent=True %}
# this would expand to
data-reflex="click->MyReflex#method"
data-param="param1"
data-param2="param"
dataset-reflex-dataset="combined"
data-reflex-permanent
For the stimulus controller itself. I would suggest
# We can have several actions.
{% stimulus controller='calendar' (optional) for="calendar" (optional) v-myvalue="value" t-mytarget="target" c-myclass="myclass" action="click->calendar#next" param="param1" %}
# Either "controller" or "for" needs to be defined. controller would expand
# to data-controller="calendar" `for` would be used if this is applied
# to a child element of where the parent already has controller defined.
# Ie data-controller is omitted, but the "for" value is used for to get the correct
# data.
# all in all this would generate the following code
data-controller="calendar"
data-calendar-myvalue-value="value"
data-calendar-target="target"
data-calendar-myclass-class="myclass"
data-action="click->calendar#next"
data-param="param1"
I think this hits the balance of giving flexibility while still being less verbose. Plus it's possible to do validation if you are so inclined. It might be that you can't write t-mytarget
ie using hyphen. If not, we could have this be t_mytarget
.
And just a note, it looks like rails use this as a template tag.
{% data param="1" param2="2" %}
which would expand to data-param="1" data-param2="2"
Feature Request
Currently reflex attributes have to be hand-craftet inside the templates. A custom tag could help users to get these right.
Is your feature request related to a problem?
No
Describe the solution you'd like
I suggest a template tag like:
{% stimulus 'lazy_load_reflex#increment' count=count increment=1 %}
which would be used in a Template likeand would generate the following HTML (formatted differently)
PR link
https://github.com/jonathan-s/django-sockpuppet/pull/60