I’ve tried to search the forum to find something that speaks to this question, but I didn’t really find a “foundations” kind of answer. I’m new to Hotwire - still just reading the docs and watching YouTube videos. But, one thing that isn’t quite gelling in my head is when to actually add Hotwire to a project? I understand that Hotwire takes a progressive enhancement approach to web development. But, does that mean that the entire suite of features in my app should work without Hotwire? And, that I should only start adding Hotwire and decomposing into frames once all the “standalone page” versions of interactions are complete?
I’m sure the answer is (as always), it depends; but, if anyone could share their general approach, I’d greatly appreciate it!
My biggest concern of going the full “progressive enhancement”, “after app is working” approach is that it might take much more refactoring to get a good UX once I start to add Hotwire. As opposed to working with Hotwire from the beginning and being able to plan for it as I go.
Welcome! And thank you for verifying your Senior Dev Cred™ by invoking the important qualifier “it depends”. You’re right, there are many ways to look at this.
This ethos brings along some tremendous benefits to you as the application designer, because it literally forces you to consider the relationships between actions and data changes, and can often expose you to some hard problems that you may be papering over using a higher-level approach (or even just old-school Ajax). It literally forces you to think in REST, which is not bad at all.
When you are first designing an app, I find it is often fastest to build out entire pages, however complex, as single templates, then start going through them with tweezers and pulling out individual features as partials (still just working with a single page render here, not moving into rendering those partials separately for a sub-page update).
Once you’ve done that, and ensured that everything still works, you can move on to making the appropriate additions of sub-page renders. Those can be nothing more than a modern equivalent of UJS, where a form request only updates the relevant parts of the page. (Think wrapping a form in a Turbo Frame.) But the same set of partials can also be used to make multiple changes to a page using Stimulus controllers or Turbo Streams.
And the beauty of this approach is that because you’ve decomposed the page at the template level, you can make any of those templates live in any context. If you want to use Action Cable to broadcast the changes to all users on the same page, that can literally be as simple as adding an after_save callback in the model or an after_action to the controller.
And if someone comes along with an older browser or a tin-foil hat, they won’t have a completely awful day.
I agree that building out full-pages first seems like it would lay-down all (or a lot of) the ground work for future optimizations and AJAX’ifying UIs and doing frames. It’s just tough because I’m so used to buiding out SPAs using Angular that I think in terms of complex UIs. Now, the struggle if figuring out how to think in terms of simple UIs that can become more complex with some “sprinkles.”
I think one of my big mental blocks right now is figuring out how/when to using a Stimulus Controller vs. when to try and use a Turbo-Frame with an embedded Form. To paraphrase DHH in other matters, my gut says to over-index on Frames first, until it “hurts”; and then maybe start to dial it back and use Controllers.
Clearly I’m just trying to find a path without having any experience yet Not sure learning ever works that way. Anyway, I appreciate your insights, thanks a lot.
Ha ha, I remember when jQuery was revolutionary Actually, from what I’ve been seeing, this feels a little bit like returning to jQuery where the DOM often held the state / source of truth. Seems like Hotwire uses the DOM for a lot of the config. So, to that end, feels like coming home.
DHH, Hotwire creator/contributor
When is a Hi-Fi experience necessary?
What you want to do is identify the 5 or 10% of your app where you really want the Hi-Fi experience, there you pay 100%, you build it and it takes more time. For the other 90 to 95%, you get it on sale.
Sam Stephenson, Hotwire creator/contributor
If the web offers us guidance, take it. The web gives us <a> to change pages and <form> to change server-side state, so that’s what we’ll use in our app. That way we have fewer decisions to make and we’re responsible for less code.
Until Hotwire, it’s been a long time since feeling I had the library of tools to build a performant and stable application in the velocity we are building. For me, the benefits of progressive enhancement are:
Stability (with all business logic on backend)
Cross Platform (web and mobile app)
I think having system tests that target a js-off state for all important pathways is probably the practical way into making it happen on an app.
I’m not seeing anybody talk about a system testing suite for the js-off scenario!
Maybe that should happen.
We would have to fake the js-off without turning it off in the browser, because capybara probably needs it for issuing the user interactions. But maybe just omit the js from loading while in this scenario.
It would have to be a different path in the system test (because an altogether separate system test would hurt). In the system test, there could be light branching for those times when the interaction with js-off is different (say, it changes pages and goes to the edit endpoint for that resource instead of having the turbo-frame inline editing). Short if js_off; else; end branches throughout.
Even without automated testing, I can see it becoming harder to manually test interfaces once they get progressively enhanced. I’m still pretty far away from even having to do that - still just trying to wrap my head around the whole frames-based approach.
But again, we see progressive enhancement as more than just supporting no-js.
I have a Rails app with event registrations. The models are Event [main event page] → EventDate [event recurrences] → AudienceMember [registrations] using has many/belongs_to, and the audience member registration form should ideally be on the event page itself (the show action). This lends itself extremely well to Turbo Frames. In the olden days, I’m sure this could be done with a super-complex nested form workflow that would make my head spin or use some weird ajax, but even then it would be hard since the audience_members/new.html.erb is supposed to be open while everything else about events is locked down and secure.
With Turbo Frames I can keep the audience_members/new.html.erb separate (with the form getting the event_date_ids into a dropdown through params passed from the event), and use a frame to show this view in events/show.html.erb. Like this:
<%# app/views/events/show.html.erb %>
<h1>Event main page</h1>
<p>Bla bla bla description</p>
<h2>Register for event</h2>
<turbo-frame id="new_audience_member" src="/audience_members/new?event_date_ids=1,2,3">
<%# Show spinner if there's a delay showing the frame %>
<div class="spinner"><svg><!-- spinner markup --></svg></div>
<%# Fallback content - link to the standalone form %>
Note the noscript tag inside the turbo-frame to provide a fallback link to the frame itself. I hide the spinner when the html element has a no-js class.
This is great, I’m actually trying to do something with a similar model. As a study context for this, I’m creating a “Tips” app that allows you to remember tips from holiday to holiday (ex, I gave the postal worker $20 last year). To that effect, a “Tip” is:
A person (tippee)
An event (optional, ex “Christmas 2022”)
An amount (ex, $20)
When I go to create the “Tip”, I would love to have a “Create new tippee” (like your “new audience member”) in the same form. So, you either select an existing person from a drop-down or create a new person on-the-fly.
At first, I was going to try doing it like a <turbo-frame>; but then I realized that I would end up having a <form> element (the inline Tippee form) nested inside another <form> element (the overall Tip page form). That said, I’m super new to this, so maybe that’s not a problem - or maybe the inner form would somehow be stripped-out? I’m not sure.
My next thought is to open the “new tippee (person)” form in a Modal window, and then emit some sort of event when it is done that the other form (“tip”) can listen for and use to update the Person drop-down menu.
Anyway, forgive me thinking out-load here - I think I really gotta go read the Hotwire docs front-to-back to get a better sense of what is possible.
Even hey.com does not follow the hard line of progressive ehancement. A few months ago I tried navigating through the app with JS turned off and I believe you can’t even send an email because the SEND button is inside a JS toggled dropdown.
That is not to say that they did a poor job at it, but to show how hard it is to be fully progressivily enhanced.
I’m really struggling to wrap my head around this stuff. Please allow me to just “stream of conscience” for a minute.
In Angular, triggering a modal window would be relatively straightforward because I would have a Layout for modals and I would have a View that was specifically designed to be in a modal. In a Hotwire context, from what I understand, there is no sense of being “designed to be in a modal” since the modal layout itself would be a progressive enhancement. Which means, I need to have a View that works as a standalone page; but, then can be progressively enhanced to be in a modal window.
The problem with that (from what I’ve been reading), is that the “modalized” version of the view would have to have a <turbo-frame> with the “modal” ID and a modal controller that handles all the modally bits (such as using Esc key to close the modal). But, this should not be there in the non-modal version since having that Controller in place wouldn’t make sense.
Now, one solution to this might be to create a modal-ready version of the View (ie, one that is only ever intended to be a modal and has the <turbo-frame> embedded and ready to go. But, the problem I see with this is that I am now moving away from the concept of progressive enhancement and I’m moving towards a “SPA framework”. Which, feels like it’s defeating the whole point of Hotwire.
Anyway, I don’t have a solid question in this rambling - just expressing my frustration in adjusting my brain to work in a Hotwire context.
When the UX team asked us to change the Add view to a modal, one of the developers rolled his eyes and said “this is going to take some time”. Because of past experience in other frameworks. Afterward, he was laughing at the simplicity of the solution. I was also pleasantly surprised.
That sounds really great. I’d love to hear just a little bit more about what you had to change to get the Add view to work a a modal - just I can start to build-up a better mental model of how things can be rewired like that.