So, my question is really about the list of target names, and what that enables. Or, to be less pedantic, why would a developer want to give a single target element multiple names (especially since they are in the same controller)? This functionality must enable some kind of use-case; but, I’m not seeing anything about it discussed in the documentation.
Imagine a form with two buttons, save and delete. On click of either button I disable all form buttons to prevent disable clicking. On delete I add a css class to the form to animate it disappearing before the turbo-stream response removes it.
Delete button includes button target and delete target
Save button includes button target
I’m doing this on one of my controllers! My code is weird and messy, and won’t make sense out of context, but the short version is that I have two select dropdowns within one controller scope, but an action (a response to a dispatched event) is supposed to programmatically change the selected option on just one of them.
Unless I rewrite to use two controllers and dispatch yet another event (or use outlets), I need two targets on the same node to make it work. So probably strictly not necessary, but in my case it simplifies the code.
Here is some code. It’s a simplified case without form labels (accessibility issues), but illustrates the use case.
Both dropdowns set a display: none on filterable nodes that doesn’t contain the option value as class name. But the second one contains more actions (an option to change the dropdown programmatically if location.href is “/events#theatre”), so it needs a way to let the controller method (dropdownChange) affect only one of the targets.
This is super interesting! I see that you are calling multiple controller methods based on the same action (ie, change triggers createStyle(), filter(), and hashChange()). In the Angular world, I probably would have encapsulated that all into one method like, handleChange(); and, then had that method call those other three methods behind the scenes. So, it’s cool to see how other people go about breaking up the logic.
Also, thank you for including a hashchange@window event - that springs to mind all kinds of interesting functionality. As I mentioned in another thread, I come from the Angular world, and in Angular we can inject a Router service that gives us that kind of insight; but, in Hotwire, I had no idea how I might change the DOM in response to things like the hash; but, seeing this connects a lot of dots
Cool that it was useful! I got some inspiration from Matt Swanson’s blog post Writing better Stimulus controllers. It’s about Stimulus 1.0, but most of the general advice is still good.
I wrote the category-filter controller two years ago and I know more now, so some of this could probably be simplified. (createStyle() is there just to write a few lines of CSS dynamically, and I should just move this into my core stylesheet.) But the core concept (using one controller to target two nodes with one method and one node with another) is sound, I think.
This controller is actually an outlier in my app, because as a general rule I prefer controllers to be as small as possible (per Matt’s advice). Then I attach different controllers (sometimes more than one on the same node) whenever needed to add behaviours to parts of the DOM. It’s actually possible that the category filter controller could be split in two or three with the help of custom events or outlets, but it’s working quite well for the time being.
Ha ha, I’ve read that article like 3 or 4 times so far at different points in my journey. The more I learn, the more I come back to it with more context. I do really like the idea of having small controller surface areas.
One thing I’ve been toying with in the back of my head (in terms of small controllers) is adding additional form fields on-the-fly. Imagine a list of Pets, and a button for “Add another pet” that just adds another input. At first, I thought a “Form Controller” would makes sense (this is the Angular way). But, now, I’m thinking can I make some sort of “Template Appender Controller” that does nothing but take a <template>, clone it, and add it to a container? Then, I could use that in a number of places.
I’m not saying my idea is correct - only that I’ve been trying hard to think about the “behaviors” and not about the UIs, per say.
One of the main motivations for avoiding sending requests to the backend is to save server resources in order to help make the application run faster for all users. This is the SPA way. I asked this question early on when I first began experimenting with hotwire in 2020. The irony is, after more than 2 years building applications with hotwire I can now say my applications run much faster with hotwire than they ever did with an SPA framework.
The genre form would contain an action targeting the same page and a submit button. After the user selects the option they click the submit button.
The page then reloads with the genre option still selected and the category form with a populated select, an action on the form and a submit button.
Use stimulus to submit itself on select change.
At this point with turbo running you might be “good” and require no additional enhancements. To determine if more work is required, some questions you might ask yourself:
Does the user experience a major/jarring scroll position change after submit?
Do we want to update the page without updating the browser history?
Does the backend or frontend HTML view contain too much logic and I want to separate the concerns?
If answering “yes” to at least of the above questions, I then reach for a turbo-frame or turbo-stream element to enhance the user experience or simplify the logic.
@tleish I really like this approach—asking yourself if you can build it without Hotwire first. I’m actually trying to follow this kind of mentality in my learnings. As I’ve been digging into Hotwire, I’m trying to create little demos for myself over on GitHub: ColdFusion + Hotwire Demos. In that code, the first thing I do when I start a new demo (usually by copy/pasting the previous demo) is go into the main page layout and comment out the <script> tag. Then, I try to build the demo using just the traditional GET and POST requests. Once I have that working, I add the <script> tag back in and try to fix whatever started breaking.
The most frustrating part of this is that getting the ColdFusion server-side rendering to work is relatively easy - it’s the sprinkling in Turbo that is the hard part