00:00:18.800
Good morning, cats and kittens! My name is Aji.
00:00:24.779
My pronouns are they/them, and I work at a place called Thoughtbot.
00:00:31.560
I usually tell a little silly fact about myself at the start of my talks, but this time I'm going to be a little more real about it.
00:00:37.020
I'm still new to this Hotwire stuff. I was up late rewriting this talk all last week because I had a Hotwire suggestion while pairing.
00:00:42.719
The response I got was, 'Oh honey, no.' My pair didn't say that, but that's how I heard it as they linked me documentation about just how wrong I was.
00:00:50.039
But also, something clicked, right? I got a little further along in my understanding than I was before.
00:00:56.039
If only my anxiety would learn that embarrassment at work usually leads to being so much smarter afterwards, I could look forward to it instead of like this.
00:01:01.980
I feel like I'm having a harder time wrapping my head around this than everybody around me. I think it's because I'm so hardwired to think in React.
00:01:07.200
Every moment where I've taken a leap in my skills with this whole Hotwire thing has been because another thought pattern that served me well in React land has fallen away.
00:01:13.500
I tell you that because I'm trying to save a few people who are making a similar change in their day-to-day tooling or code bases a little bit of that cringe.
00:01:19.020
A little bit of that 'Oh honey, no.' I already did it so you don't need to do it too.
00:01:25.080
But like many of us, if not all of us here, I identify as a Rails developer.
00:01:31.680
Ruby is the first programming language I truly learned; I do not count BASIC from back when I was 12.
00:01:37.979
But like many in the Rails world, I have spent the last few years using Rails not as a full stack application framework, but in API mode.
00:01:43.799
I’ve been sending JSON over the wire to various versions of a React frontend.
00:01:51.180
Hotwire or React? I'm not going to say that one is better than the other.
00:01:57.659
I'm barely, if at all, going to spend time highlighting strengths and weaknesses or talking about trade-offs. This talk isn't to convince you to switch.
00:02:03.780
We all know that it's trade-offs all the way down, and the frustrating answer to every question is, 'It depends.'
00:02:09.599
The moment in question today is beyond that decision anyway. The application you're building or are about to build will be written without React, using nothing but Rails, Hotwire, Turbo, and Stimulus.
00:02:15.239
Maybe Turbo Native, but that's a different talk.
00:02:20.580
If you've written a lot of React lately and are new to this Hotwire world...
00:02:26.099
If you think in components and hooks rather than Turbo Frames and Stimulus controllers, you are where I was six months ago and you are who I'm here for.
00:02:31.440
And if you already know a little bit about Hotwire, just don't heckle me during the entry-level stuff, okay?
00:02:36.480
If you're unfamiliar with the players, let me introduce you before we get too far along.
00:02:43.620
Hotwire is, as of Rails 7 in 2021, the framework's built-in solution for the frontend.
00:02:49.800
It's made up of two parts: Turbo and Stimulus.
00:02:56.040
Turbo is the successor to Turbo Links. It accelerates links and form submissions by capturing interactions and submitting via JavaScript.
00:03:01.500
This allows for partial updates and independent loading of portions of the page.
00:03:08.239
Stimulus is a system that allows for graceful interaction between narrowly scoped JavaScript controllers and our HTML.
00:03:15.060
For those interactions that need to exist completely on the client side.
00:03:23.099
And before six months ago, these were not in my wheelhouse.
00:03:29.099
Okay, I love React. I know that is a weird thing to say at RailsConf, but I really do!
00:03:34.620
Like Rails, it just fits with how my brain works.
00:03:40.260
The declarative UI, reacting to changes in the state of a system made of component trees, with data down and actions up.
00:03:47.640
And sure, it's produced by a shady mega-corporation.
00:03:53.519
But it's not like it's the only place in tech where we love a technology despite its founder. On top of React, let's add in GraphQL, which I also stand by.
00:04:05.519
Even our much-beloved backend starts to look less and less like Rails, with essentially no controllers and only a single endpoint.
00:04:14.840
Even though my current situation leads me to write a talk about how to use Hotwire and leave React, I'm pretty sure I still have more fun writing React.
00:04:22.620
It just fits with how I conceptualize the front end and complex UIs.
00:04:28.800
This is what made the transition for my current project particularly difficult.
00:04:36.000
This project is a greenfield application, lots of interactivity with the user including chat and real-time updates to information that have that real SPA kind of feel.
00:04:43.680
Well, what is SPA anyway? Webster's Dictionary defines it as a commercial establishment providing facilities devoted especially to health, beauty, and relaxation.
00:04:49.560
But my client is in health insurance, so none of that is covered.
00:04:56.760
When we talked about SPA, it meant single page application. It's such a broad term; it can mean anything from the literal web app implementation that loads from a single web document and updates the body content.
00:05:03.060
That is from MDN, to describing the experience that users have come to expect: a fast, responsive, and interactive web application that provides a smooth and engaging user experience with real-time updates, mobile responsiveness, and fluid interactivity.
00:05:10.800
This one is from me and chat GPT; it's like a 50/50 collaboration.
00:05:15.060
So giddy at the opportunity to not pull in a front-end framework.
00:05:22.840
Both my team and my clients were excited, confident that Hotwire would bring that SPA browser feel.
00:05:29.579
And despite my fondness for React, I was excited too!
00:05:37.380
Server-rendered HTML, just like Mom used to make!
00:05:43.680
It's an idyllic return to the first principles of the web. Hotwire sounded analog and peaceful after years of JavaScript loading up on new features like toilet paper in 2020.
00:05:50.520
What I didn't appreciate at the time was how different the problem-solving techniques and patterns were truly going to be.
00:05:56.220
So we're getting to the heart of my motivation for being up here with you.
00:06:03.600
A mental model is an explanation of someone's thought process about how something works in the real world.
00:06:10.500
So I want us all to think of the solar system.
00:06:15.600
Think of the solar system. You probably conjured up something like this, right? This picture of planets orbiting the Sun probably reflects the mental model of most.
00:06:22.260
Because this representation is good enough for our day-to-day interactions with Jupiter, the representation we carry will influence how we approach problems.
00:06:30.060
An incorrect mental model led Senator Ted Stevens to claim that streaming video had clogged the internet's series of tubes, causing his emails to arrive days late.
00:06:36.720
For us here in this room, the wrong mental model can leave bugs in places we would never even think to investigate.
00:06:46.020
And although React and Hotwire are solving the same sorts of problems, the metaphors, concepts, and building blocks that make up the tools we use to solve those problems aren't really compatible.
00:06:54.300
And I wasn't going to be a Ted Stevens in this project forever.
00:07:02.280
So I'm going to describe my mental model of working with React.
00:07:09.660
I bet it's pretty close to how many of us think in that system.
00:07:15.060
It's a tree of components. The rendering of this tree starts at the root; that's usually App or something similar.
00:07:22.440
Each component along the tree has the ability to render DOM elements, nothing, or more components.
00:07:30.120
The components deal in data, manipulating it or passing it to child components as props.
00:07:36.960
Data can only flow down the tree from parent to child.
00:07:43.140
Data flow is fundamental as a React developer, and it gives us one half of that React mantra: data down.
00:07:50.400
In this model, as we're looking at it, network requests are irrelevant.
00:07:56.700
I bring this up because trying to equate them between this model and Hotwire will only be confusing.
00:08:03.120
Okay, but it's web development, right? How can network calls be irrelevant?
00:08:10.140
Let's focus on this component here for a moment.
00:08:15.600
The component gets some props, uses this data in a function to derive new state, and uses that state to conditionally render children.
00:08:22.560
Now let's say that this component gets some props, uses this data in a network request that gives it new state, which it uses to continually render children.
00:08:28.440
They're functionally equivalent, so there's nothing you can do from elsewhere in the tree to determine if that change was local, a hook, user input, or even just hard-coded.
00:08:36.360
So, for our purposes today, forget the React Network calls and don't try to find equivalency with Rails.
00:08:43.080
The last piece to this model is callbacks.
00:08:49.680
As with data, a component can send a function down the tree which can later be invoked by a child component.
00:08:55.560
That is the only way to return data back up the tree to components higher in the order.
00:09:02.040
And that's where we get the second half of that React developer mantra: actions up.
00:09:08.520
Those are our building blocks of a React application: the components, the state, and the actor.
00:09:15.279
In this system, the state drives us forward. A change in state causes a reaction in the component tree, and we can think about how to move state around the tree by remembering: data down, actions up.
00:09:29.340
This was the model that I had been working with for years.
00:09:36.960
The solution that came easily to me started with trees and state, but Hotwire was impenetrable until I could leave the old problem-solving models behind.
00:09:43.680
Conference talks, I find, are not particularly great for picking up on syntax and specific technical details.
00:09:51.300
So unless you're watching this virtually from the future (in that case, hello! Rails greater than seven is pretty great, isn't it?), I'm going to get through as many examples as I can.
00:09:59.700
And I encourage you to be present, follow along, and pick up the concepts and mental models without trying to read every line of code.
00:10:05.520
For the rest of it, I've got some resources at the end, and I'll invite you to keep an eye on the Thoughtbot blog for write-ups of these examples in more detail.
00:10:12.840
Alright, I'm going to take us through building and then enhancing a site with Hotwire.
00:10:19.380
What site? The Ruby on Rails Guides!
00:10:25.099
I hear you out there! Yeah, I do. Saying: 'Aji, is this a plug for your new podcast, The Tightly Coupled Book Club,' where you and your equally charming and vastly more intelligent wife, Mina, and fellow Rails developer read the Rails Guides cover to cover and discuss it like a book club?
00:10:30.120
First of all, how dare you?! That is a baseless accusation, and that is so obviously true.
00:10:37.680
I am not going to dignify it with a response other than please follow us in your podcaster of choice, rate and review us on iTunes, and buy a Casper mattress.
00:10:43.680
Upon arrival at the guides, we're greeted with links that each open their own page.
00:10:49.080
Therefore, we will refer to these as pages within a page.
00:10:54.540
There are chapters within chapters, and there are paragraphs.
00:10:59.700
And that's where we'll start: with pages that have many chapters and chapters that have many paragraphs.
00:11:05.880
The structure of our exploration will be mostly to the tune of 'if that in React, think this in Hotwire.'
00:11:11.520
I say mostly because the first thing I want to do is immediately not do that at all.
00:11:16.740
There is a mindset difference in approaching assembling a site with Hotwire.
00:11:21.699
It is this: start as if you didn't have Hotwire at all.
00:11:27.360
If we follow Rails conventions, we'll be able to use Turbo to enhance the experience by adding only a few lines of code and little, if any, JavaScript.
00:11:34.200
This was probably the biggest hurdle for me, thinking that the document would be partitioned off and calling for new components, instead of simply requesting a page.
00:11:41.160
Let's start with these three routes, and we'll take a look at the mock-ups.
00:11:48.780
The first is Pages Index; this is going to be the layout that we're working in.
00:11:54.299
A Rails red header up at the top, the pages list on the side, and because the user hasn't chosen a chapter yet, we have nothing to load.
00:12:01.640
So why not go for some Rails nostalgia, and here's Page's show?
00:12:07.320
Same header, chapter placeholder, but the page list is expanded for the selected page, and we can see the chapter titles.
00:12:14.440
And last is Chapters Show.
00:12:21.480
Maybe we saw this coming, but it's going to show the chapter.
00:12:27.480
So I've set up a partial that will help us deliver the three-part layout that we see here.
00:12:32.400
We'll call it Navigation Frame. So, it holds the header, which doesn't really change.
00:12:39.540
And these yield calls here are named to be able to target the specific locations later.
00:12:45.300
If you're thinking about putting this in a layout, hold on to that thought.
00:12:51.120
So we're going to go to the chapter show template to get a feel for where we're going to be spending a lot of our time.
00:12:58.020
These two content four calls matched up with the named yield regions we saw before. They're holding all the content for those portions of the page.
00:13:04.320
And that content is in these two partials, we'll open them up when they become relevant.
00:13:10.980
But let's take a look at what the site looks like from here.
00:13:18.840
Pretty good so far. Navigation frame is holding up the layout, and those partials iterate the content pages and chapters on the left.
00:13:24.480
Chapters plus paragraphs on the right.
00:13:30.420
One of the building blocks of Turbo is the Turbo Frame. It's described pretty often as being scoped navigation.
00:13:36.600
So look at the chapter region of the layout: if that box was a Turbo Frame, interactions inside it would, by default, only affect what's inside the frame.
00:13:42.600
So what is the React equivalent of scoped navigation?
00:13:48.980
In React, scope navigation kind of doesn't make sense. You follow along the rendering path and get to the end.
00:13:54.300
There just isn't a direct link here; navigation is already scoped.
00:14:01.200
It's not the wild west like a web page; navigate wherever the anchor tag takes it. We're hardwired to know the only way to that other component is back up and over.
00:14:08.220
So what even is a frame in this context?
00:14:15.660
From the layout, I see two candidates for Turbo Frames: the left that switches between Pages Index and Pages Show, and the right, which shows a single chapter at a time.
00:14:21.240
We'll stick to the chapters template, but know that we'll need to put this frame in the same place on all three pages.
00:14:27.720
Index Pages Show and here in Chapter Show. We want the Turbo Frame to wrap everything that should update when a link inside itself is clicked.
00:14:34.720
That's the whole chapter region, so we'll make it the topmost element in that block.
00:14:40.440
Rails provides a Turbo Frame tag helper; this renders out to a custom element of, you guessed it, Turbo Frame.
00:14:48.060
And you see there, it makes the first argument an ID.
00:14:53.640
That's actually it! That's all you have to do.
00:14:58.920
What does this look like in the browser?
00:15:05.520
That is a different video! I promise we'll be able to see the difference if we take a look at what HTML is actually being sent over the wire.
00:15:11.520
This is the HTML delivered by the server when we first visit Chapter One.
00:15:17.640
And I certainly do not expect anyone to be able to read this.
00:15:23.040
I'll take you through it: these lines here are the head tag, JavaScript, CSRF meta tags, page title, style sheets... nothing out of the ordinary.
00:15:29.880
The body tag starts with that Rails red header that we've been working with.
00:15:37.440
Here's the page region; there is a selected page with three open chapters currently.
00:15:44.160
And this is that for a selected chapter with all of its paragraphs intact; that is, all of the markup of our page.
00:15:52.620
From Rails's head tag, our layouts, pages, and chapter...
00:15:58.620
And right here at line 66 is our Turbo Frame.
00:16:04.560
Even so far, it’s as expected! But what happens when we click that link from inside the frame?
00:16:10.200
That next chapter link... alright, the second chapter can't be that much shorter, right?
00:16:16.020
So there, this is the chapter, there is the page list, there's that header, and that's the head tag... it's empty!
00:16:23.040
When Turbo intercepts the clicks, it sends the request via JavaScript, including a special header, Turbo Frame in the frame's ID.
00:16:30.840
That way the server knows that the page it's talking to is Turbo enabled. It already has the contents of the head tag and doesn't bother sending it!
00:16:37.200
You know what else it doesn't send? The layout!
00:16:43.920
So, we didn't put the navigation frame code there, and the Turbo Frame can't be there either.
00:16:51.000
And this is why!
00:16:56.640
So, we'll take a little peek into that Hotwire mental model.
00:17:02.880
We make our first request and receive chapters one. Remember this section of the page is encased in a Turbo Frame with the ID 'chapters'?
00:17:09.180
When we click the next chapter link, Turbo sends a request for chapters two, and down it comes.
00:17:15.600
It's essentially the whole page minus the head tag.
00:17:22.200
Turbo is going to inspect this response and look for a Turbo Frame element that matches the one it asked for of chapters.
00:17:27.720
If it finds it, Turbo will punch out the old chapters frame and throw it away.
00:17:34.800
It will remove the new chapter's Turbo Frame, throw away the rest of the response, and switch the new frame in, having only updated the part that needed to change.
00:17:41.520
Sounds kind of Reacting, right? React won't update parts of its component tree and therefore the DOM when data dependencies haven't changed.
00:17:47.640
Neither will Turbo update the DOM if it hasn't been directed to.
00:17:55.020
Alright, let's jump into another example! Let's apply a Turbo Frame to the pages region.
00:18:03.240
Just as we wrapped the chapters region in a Turbo Frame, let's do the same with the pages.
00:18:10.320
Same thing, we're ready to go!
00:18:15.600
It's pretty great to be able to take advantage of this functionality with so little code.
00:18:21.120
So let's see how this improves the experience in the browser.
00:18:29.340
Okay, clearly we have broken something.
00:18:37.080
If we think about the interaction that Turbo is having with the response it should become clear after clicking that link.
00:18:43.920
It's gotten rid of the old Pages frame and replaced it with the new one.
00:18:50.520
But if the left sidebar is scoped to that frame and the chapters were never in that frame, this is a pretty common use case.
00:18:58.680
Right? You click on a link in the header or sidebar of a web page and the main section changes.
00:19:05.520
In React, we find ourselves needing to reach another branch of the tree.
00:19:11.320
We know that it's going to have to go through the closest mutual parent's component.
00:19:17.320
That parent component will send a callback down one side and then send props across and down the other.
00:19:23.880
So, think this in Hotwire.
00:19:29.980
So, we're on the page partial. It's not the show or index template; it's the models partial where we iterate the chapters into links.
00:19:36.960
We can tell this link which frame we want it to swap out by adding a data attribute to the anchor tag.
00:19:43.320
Turbo frame tells Turbo to target the chapters frame from the response instead of the frame that originated this request.
00:19:49.560
Turbo action tells the URL to update to the address of this link; that way, our users can bookmark specific chapters and come right back later.
00:19:56.700
Let's see the chapter's controller.
00:20:02.460
So, we've had this query for page.all sitting here this whole time.
00:20:09.300
If we peek at the top of the chapter show template, we are rendering that page index and it needs the data somehow.
00:20:16.500
Even so, I don't particularly love that this page data is getting mixed up in our chapters controller.
00:20:24.960
And as you might expect, there is a Turbo answer here.
00:20:31.980
We'll start by taking out the offending database query and moving it over to this chapter show template.
00:20:38.280
And we'll get rid of the page list partial; it's why we needed that query, so bye!
00:20:44.520
We'll again add an option; this time, the Turbo Frame source attribute tells the frame to begin Turbo navigating as soon as it's loaded into the DOM.
00:20:50.880
So, in a React component version of what we're trying to accomplish, a useEffect hook makes a network request and places the response in the component state.
00:20:57.360
The empty dependency array tells us it's going to do that just once on mount.
00:21:04.680
And if we don't yet have data for the pages, we'll render a loading message.
00:21:10.560
Once we have it, we'll render the page index.
00:21:17.160
That's a pretty common pattern in the React world: to eager load some data as soon as the component is activated.
00:21:23.040
Display a loading message for the user.
00:21:29.880
So when the page loads, Turbo will notice the URL and fire off that request.
00:21:36.000
But the React version was able to handle a loading state as well, right?
00:21:42.180
Well, the block inside the Turbo Frame will be rendered in the same manner as before.
00:21:48.120
It'll be replaced, though, as soon as the response comes back.
00:21:54.180
So let's take a look at this live.
00:22:01.080
I'm going to artificially slow the server's response so that we can actually see the loading state.
00:22:06.960
Ah, great! Fix it!
00:22:12.240
The most exciting thing about this here for me, though, is that each of the clicks on the chapters on the left are also visiting the chapters show action.
00:22:19.680
And if it were a full page reload, it would hit that slow query all over again.
00:22:25.620
But the navigation is contained to our Turbo Frame on the right. Everything stays snappy!
00:22:31.260
Look here at the newly revamped Rails Guides: we want to make contributing to Rails easier than ever before.
00:22:38.160
So in that spirit, we are announcing live inline editing of every paragraph on the guides.
00:22:45.600
Because there's no way that this could go poorly!
00:22:52.920
I feel like this is a wheel I've either reinvented or seen reinvented several times across the React products that I've worked on.
00:22:58.680
We need to be able to toggle between read mode and edit mode in line, and have live updates on save.
00:23:05.520
Given the ease of conditional rendering in React, having a component that toggles between two children is actually pretty straightforward.
00:23:13.680
We'll need some local state to handle the state of the conditional.
00:23:19.920
We can pass into components one for each view and edit, and each of them can handle their own manner of viewing, editing, and persisting however they like.
00:23:26.520
The view-edit wrapper can very easily be separate from any of that logic.
00:23:31.680
It's even pretty trivial to be resource-agnostic from this component's point of view.
00:23:38.160
And we send down a callback to handle the toggle for each of the components to implement however they see fit: button, keyboard shortcut, timeout, whatever!
00:23:44.280
So we'll start by looking at the paragraph controller.
00:23:50.340
This is a standard off-the-shelf Rails controller that very well could have come with Rails G scaffold.
00:23:56.220
It only has edit and update because we're going to leverage some of our other infrastructure otherwise.
00:24:03.660
And we can see some of that at play here.
00:24:10.680
We won't get into some of the nifty things Turbo can do with forms today, but instead we're going to render the paragraphs chapter.
00:24:17.880
Which contains all its markup.
00:24:24.360
I've taken the liberty of adding the necessary routes as well.
00:24:31.680
So we'll visit our chapters partial.
00:24:37.320
This is the only place that we've been rendering the paragraph so far, right here.
00:24:44.160
The content is going straight into the CramDown gem to take the plain text from our database and turn it into markup in HTML.
00:24:50.640
Let's extract a component - I mean partial - for our paragraphs.
00:24:57.000
Okay, we're off to the races! Take a look at that.
00:25:00.540
Cool! So this is really all that we've ever had for the paragraph so far.
00:25:09.480
But we're going to start out with our new friend and wrap this in a Turbo Frame.
00:25:16.380
We've got a little something new this time around, though: DOM ID is a helper method that Rails gives us.
00:25:22.200
To make identifiers from objects, it matches up their name and their database ID as well as optional arguments.
00:25:28.020
It's a shortcut for creating a meaningful ID attribute for your Turbo Frames.
00:25:35.160
If this one was passed a paragraph with ID 1, it would generate this... easy peasy.
00:25:41.880
Some quick styling considerations, and here we are.
00:25:48.300
A link to the RESTful edit route associated with this resource, a Rails linked to Rails URL helper.
00:25:55.380
The closer we stick to convention, the easier this is going to be to fit together.
00:26:01.920
This is the most specific and kind of tightly scoped Turbo Frame that we've tried yet.
00:26:08.100
Let's look at the edit template.
00:26:16.020
To blank slate so far. It's almost a shame to fill it with the least surprising form possible.
00:26:22.080
Oh well! Can't forget the Turbo Frame tag.
00:26:27.120
Remember, we've got that narrowly focused one around the link that gets us here!
00:26:34.560
So let's take a look.
00:26:40.800
Nice! So look at that frame, scoped to an instant specific identifier, can be mighty powerful indeed.
00:26:47.520
And we've done our part to ruin the brand new Rails Guides.
00:26:54.000
There is one thing to point out: we did it because of limited time and space, but our other view templates (pages index, chapters show, etc.) had all of the places for putting together the entire page.
00:27:00.840
The paragraph edit did not. It did have the Turbo Frame that we needed.
00:27:06.300
So its limited markup did the job, but what would that route look like without the rest of the site already loaded and Turbo spun up?
00:27:13.080
Not the best UX, is it?
00:27:17.640
Alright, so keep that in mind as you're building out these sites. Alright, let's do something fun.
00:27:30.720
I mean, it's all been fun, but funner!
00:27:35.640
We've been focusing so much on Turbo we haven't talked at all about Stimulus.
00:27:41.040
This is Hotwire's client-side JavaScript component.
00:27:48.240
So often, Hotwire opens possibilities without more JavaScript, but there are some behaviors that have to take place completely on the client side, like keyboard shortcuts.
00:27:54.540
We're going to add a modal chapter search that can be triggered by hockey.
00:28:02.820
Keeping in mind React's power when it comes to conditional rendering based on a Boolean switch, let's focus on the keyboard shortcut.
00:28:09.840
Go!
00:28:11.880
We've got to useEffect that starts us off by setting some vanilla JavaScript event listeners.
00:28:16.440
And we'll be waiting to hear a keydown event.
00:28:24.180
We'll return a cleanup function to make sure the listener is cleared when this component unmounts.
00:28:30.240
Here's the keydown callback: we'll inspect the event for the key combinations we care about and update the state!
00:28:37.560
Now we are going to open up the layouts application template.
00:28:44.700
Since our attributes that define the interaction with key events are application-wide, we'll put it on a highest component straight on the body tag.
00:28:50.040
Stimulus connects to the environment through the DOM via these attributes.
00:28:55.200
The first one we'll add is the name of the controller we want to connect to.
00:29:02.520
This element data-action defines the event handlers, and the keydown event is filterable by key press.
00:29:08.880
So these two lines here are actually all that's needed to set both MetaKey and Escape hotkey handlers.
00:29:15.720
The right side of the arrow in these expressions denotes the controller and function we want those events to trigger.
00:29:23.040
The same controller hash method convention that you see in the routes file or Ruby API docs.
00:29:30.060
Now following convention over configuration, and true Rails style, the file name matches to the data controller value in the body tag.
00:29:37.260
Making this controller wake up and pay attention.
00:29:43.680
The toggle function manages a CSS class on the modal, so we'll hide it or show it.
00:29:50.940
And these target definitions, those arrays of strings, map behaviors to elements on the page that we'll identify in the HTML.
00:29:56.820
Stimulus does the DOM scanning for us.
00:30:02.700
Back to the navigation frame!
00:30:09.060
So half of this file that we've seen before. I'll get a little closer to the new stuff.
00:30:15.660
The modal, starting hidden, wraps a Rails form with a single text input and a few Stimulus-specific data attributes.
00:30:20.880
Those are the attributes that identify the elements to the controller for use in the functions that we saw.
00:30:26.880
Those targets: data-controller name and target string.
00:30:31.680
It'll submit with a GET request to a new route, and the response will be scoped to an empty Turbo Frame.
00:30:38.880
That is the target of our form. Without it, the response would have no frame to replace into, and it would take over the entire page.
00:30:44.520
So, in the chapter search controller...
00:30:49.560
We're going to perform a fairly naive search through an SQL-like wildcard implementation.
00:30:56.420
Which, in turn, renders this view.
00:31:01.920
Which iterates the matched chapters, linking to each.
00:31:10.440
And if we look here, there is a target option with the name underscore top.
00:31:15.360
This is new! We don't want the resulting links to try and be scoped into the search results frame.
00:31:21.300
Instead, underscore top tells the page to perform a full navigation to the show path of the chapter in the link.
00:31:29.880
Let's see what we've got!
00:31:34.140
There we go! Cool! That's actually the last example that I could squeeze into this talk.
00:31:40.740
So I know it's a little abrupt, but thank you all!