<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>mikewilson.dev</title><description>Development thoughts by Mike Wilson. I&apos;ve been making software for about 10 years now, and before that worked as a Business Analyst in the banking industry.</description><link>https://www.mikewilson.dev/</link><item><title>Client Side Form Validation With Stimulus and Rails</title><link>https://www.mikewilson.dev/posts/client-side-form-validation-with-stimulus-and-rails/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/client-side-form-validation-with-stimulus-and-rails/</guid><description>Client side form validation is fairly easy to do with a framework such as Angular or Ember, but how can we get client validation with just Rails and Stimulus? With a sprinkling of Javascript, we can see just how easy it is to add some client side validation.</description><pubDate>Mon, 27 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the things I miss the most when working on a non-framework application, such as Angular or Ember, is how easy it is to do form validation. With Angular, you have an in-memory version of your form, and can easily run some Javascript for doing validation.&lt;/p&gt;
&lt;p&gt;There are times when you really don’t want or need a full-blown Javascript app, which is where a tool such as &lt;a href=&quot;https://stimulusjs.org/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Stimulus&lt;/a&gt; and server-generated HTML becomes extremely useful.&lt;/p&gt;
&lt;p&gt;Fortunately, browsers have a lot of built-in validations that we can use.&lt;/p&gt;
&lt;p&gt;I know what you’re probably thinking - the native form validations UI is super ugly and extremely difficult to style. I agree! Fortunately, with a sprinkling of Stimulus, we can hook into the functionality that browsers provide, while doing all of the styling ourself.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Be sure to always validate on the server, even if you’re implementing client-side validation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;setting-up-our-form&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#setting-up-our-form&quot;&gt;Setting up our Form&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We’ll start with a very basic form in any generic Rails application. For this simple example, we’ll scaffold a post that has a &lt;code&gt;title&lt;/code&gt; and a &lt;code&gt;slug&lt;/code&gt;, both of which will be required fields.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame is-terminal&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;rails&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;generate&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;scaffold&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;title:sting&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;slug:string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;rails generate scaffold Post title:sting slug:string&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Let’s take a look at the form that Rails creates for us, and change a few things to add some validation.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;form_with&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; post, &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; |form| &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;submit&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= form_with(model: post, local: true) do |form| %&gt;  &lt;div class=&amp;#x22;field&amp;#x22;&gt;    &lt;%= form.label :title %&gt;    &lt;%= form.text_field :title %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;field&amp;#x22;&gt;    &lt;%= form.label :slug %&gt;    &lt;%= form.text_field :slug %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;actions&amp;#x22;&gt;    &lt;%= form.submit %&gt;  &lt;/div&gt;&lt;% end %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The first thing we’ll want to do is change our form to be a remote form, by removing &lt;code&gt;local: true&lt;/code&gt;. This will allow RailsUJS to handle our form submissions for us.&lt;/p&gt;
&lt;p&gt;Next, we’ll want to mark our title as a required field. This tells the browser that this form is required, but our form won’t actually enforce it just yet.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; form.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;required:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= form.text_field :title, required: true %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we load the page and try and submit, we’ll get some pretty nasty looking validation errors.&lt;/p&gt;
&lt;img alt=&quot;Not the best user experience...&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;254&quot; height=&quot;212&quot; src=&quot;https://www.mikewilson.dev/_astro/ugly-form-validation.a3tYSJjM_Z1StDgf.webp&quot; &gt;
&lt;p&gt;To remove the ugly validation, add the &lt;code&gt;novalidate: true&lt;/code&gt; html attribute to our form. This will turn off any form validation for now, but in the next section we’ll see how we can hook into that functionality with some Javascript and CSS to provide a better user experience.&lt;/p&gt;
&lt;p&gt;Here is what our form should look like now:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;form_with&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; post, &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;novalidate&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; |form| &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;required:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;submit&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= form_with(model: post, html: { novalidate: true }) do |form| %&gt;  &lt;div class=&amp;#x22;field&amp;#x22;&gt;    &lt;%= form.label :title %&gt;    &lt;%= form.text_field :title, required: true %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;field&amp;#x22;&gt;    &lt;%= form.label :slug %&gt;    &lt;%= form.text_field :slug %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;actions&amp;#x22;&gt;    &lt;%= form.submit %&gt;  &lt;/div&gt;&lt;% end %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;h3 id=&quot;add-a-sprinkling-of-stimulus&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#add-a-sprinkling-of-stimulus&quot;&gt;Add a sprinkling of Stimulus&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For the purposes of this article, we won’t go into how to install Stimulus in the rails application. We’ll assume that’s already been done.&lt;/p&gt;
&lt;p&gt;Now, we’ll want to create a Stimulus &lt;code&gt;form_controller&lt;/code&gt; that we’ll use to hook into the form submit action and manually validate our fields.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;targets&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;form submitted&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;export default class extends Controller {  static targets = [&amp;#x22;form&amp;#x22;];  submitForm(event) {    console.log(&amp;#x22;form submitted&amp;#x22;);  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;So far, this is a pretty basic form. We’ve setup a target which will be the form element itself, and created a placeholder action for when the form is submitted.&lt;/p&gt;
&lt;p&gt;Let’s go back and modify our form to use this new controller and setup our actions:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;form_with&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; post,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;                  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;novalidate&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;              &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;                  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;                  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;form.form&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;                  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;ajax:beforeSend-&gt;form#submitForm&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; |form| &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;required:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;text_field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; form.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;submit&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= form_with(model: post,              html: {                  novalidate: true              },              data: {                  controller: &amp;#x22;form&amp;#x22;,                  target: &amp;#x22;form.form&amp;#x22;,                  action: &amp;#x22;ajax:beforeSend-&gt;form#submitForm&amp;#x22;              }) do |form| %&gt;  &lt;div class=&amp;#x22;field&amp;#x22;&gt;    &lt;%= form.label :title %&gt;    &lt;%= form.text_field :title, required: true %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;field&amp;#x22;&gt;    &lt;%= form.label :slug %&gt;    &lt;%= form.text_field :slug %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;actions&amp;#x22;&gt;    &lt;%= form.submit %&gt;  &lt;/div&gt;&lt;% end %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What we’ve done is modified our form to add some data attributes. &lt;code&gt;data-controller&lt;/code&gt; specifies the name of our newly created Stimulus controller, while &lt;code&gt;data-target&lt;/code&gt; tells Stimulus which element is our form. Finally, we use &lt;code&gt;data-action: &quot;ajax:beforeSend-&gt;form#submitForm&quot;&lt;/code&gt; to tell Stimulus to hook into the &lt;a href=&quot;https://github.com/rails/rails/tree/master/actionview/app/assets/javascripts&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Rails UJS&lt;/a&gt; event that gets fired.&lt;/p&gt;
&lt;p&gt;If we submit our form now, it still goes through without validation, but we should at least see our console.log statement. Now we know we can hook into a form submit, and run whatever code we need to run. Let’s flesh out our &lt;code&gt;submitForm&lt;/code&gt; method a bit more now.&lt;/p&gt;
&lt;h3 id=&quot;validating-the-form-with-javascript&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#validating-the-form-with-javascript&quot;&gt;Validating the form with Javascript&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Fortunately, even though we’ve turned off form validation using &lt;code&gt;novalidate: true&lt;/code&gt; in our form, we can still hook into the attributes that the browser applies to the form for us, such as &lt;code&gt;:required&lt;/code&gt; or later on, &lt;code&gt;:invalid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What we want to do is to search our form for any input elements that are &lt;code&gt;required&lt;/code&gt;, and check to see if they have any content. Let’s see what that looks like:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;targets&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;submitForm&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; isValid &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;validateForm&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.formTarget);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// If our form is invalid, prevent default on the event&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// so that the form is not submitted&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;isValid) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;event.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;preventDefault&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;validateForm&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; isValid &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Tell the browser to find any required fields&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; requiredFieldSelectors &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;textarea:required, input:required&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; requiredFields &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.formTarget.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;requiredFieldSelectors,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;requiredFields.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// For each required field, check to see if the value is empty&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// if so, we focus the field and set our value to false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.disabled &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.value.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;focus&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;isValid &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; isValid;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;export default class extends Controller {  static targets = [&amp;#x22;form&amp;#x22;];  submitForm(event) {    let isValid = this.validateForm(this.formTarget);    // If our form is invalid, prevent default on the event    // so that the form is not submitted    if (!isValid) {      event.preventDefault();    }  }  validateForm() {    let isValid = true;    // Tell the browser to find any required fields    let requiredFieldSelectors = &amp;#x22;textarea:required, input:required&amp;#x22;;    let requiredFields = this.formTarget.querySelectorAll(      requiredFieldSelectors,    );    requiredFields.forEach((field) =&gt; {      // For each required field, check to see if the value is empty      // if so, we focus the field and set our value to false      if (!field.disabled &amp;#x26;&amp;#x26; !field.value.trim()) {        field.focus();        isValid = false;      }    });    return isValid;  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What we’re doing is looking up any required fields and manually checking the value, to see if they have any values. If any required field is empty, then we return &lt;code&gt;isValid&lt;/code&gt; as false. In order to tell the Rails UJS library not to submit the form, all we have to do is &lt;code&gt;preventDefault&lt;/code&gt; on the event, and the form submission will be cancelled, and the required field will be focused.&lt;/p&gt;
&lt;img alt=&quot;Form Submit Prevented&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;282&quot; height=&quot;278&quot; src=&quot;https://www.mikewilson.dev/_astro/form-submit-prevented.DP8AcpW9_Z1YWabO.webp&quot; &gt;
&lt;h3 id=&quot;other-validation-types&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#other-validation-types&quot;&gt;Other validation types&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another feature that browsers have these days is pattern validation. We can tell the browser what the required pattern is on an input field, and if the input doesn’t match, we can hook into that validation.&lt;/p&gt;
&lt;p&gt;Let’s create an arbitrary example, and say that our post slugs must be required and be alphanumeric.&lt;/p&gt;
&lt;p&gt;Here is what our input field for our slug would look like:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#f8f8f2;--1:#24292e&quot;&gt;&amp;#x3C;%= form.text_field :slug, required: true, pattern: &quot;[a-zA-Z0-9]+&quot; %&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= form.text_field :slug, required: true, pattern: &amp;#x22;[a-zA-Z0-9]+&amp;#x22; %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we submit now with a valid title and an empty slug, the form submission will still go through. We need to modify our &lt;code&gt;validateForm&lt;/code&gt; method to now look for &lt;code&gt;:invalid&lt;/code&gt; elements.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;validateForm&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; isValid &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; requiredFieldSelectors &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;textarea:required, input:required&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; requiredFields &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.formTarget.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(requiredFieldSelectors);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;requiredFields.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.disabled &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.value.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;trim&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;focus&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;isValid &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// If we already know we&apos;re invalid, just return false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;isValid) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Search for any browser invalidated input fields and focus them&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; invalidFields &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.formTarget.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;input:invalid&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;invalidFields.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.disabled) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;field.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;focus&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;isValid &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; isValid;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;  validateForm() {    let isValid = true;    let requiredFieldSelectors = &amp;#x27;textarea:required, input:required&amp;#x27;;    let requiredFields = this.formTarget.querySelectorAll(requiredFieldSelectors);    requiredFields.forEach((field) =&gt; {      if (!field.disabled &amp;#x26;&amp;#x26; !field.value.trim()) {        field.focus();        isValid = false;        return false;      }    });    // If we already know we&amp;#x27;re invalid, just return false    if (!isValid) {      return false;    }    // Search for any browser invalidated input fields and focus them    let invalidFields = this.formTarget.querySelectorAll(&amp;#x27;input:invalid&amp;#x27;);    invalidFields.forEach((field) =&gt; {      if (!field.disabled) {        field.focus();        isValid = false;      }    });    return isValid;  }&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Just like before, we query the elements in the form for invalid input fields. This time, we don’t need to do any manual validation ourself, as the browser does that for us. When a browser has an invalid field, it adds the &lt;code&gt;:invalid&lt;/code&gt; attribute to it, and we can use that for querying and styling. All we have to do is find those elements, focus them and set our &lt;code&gt;isValid&lt;/code&gt; property if we find any.&lt;/p&gt;
&lt;p&gt;Now, if we try and submit a post with an invalid slug, the submission will be prevented, and the slug field will be focused. Not bad for only a few lines of Javascript!&lt;/p&gt;
&lt;img alt=&quot;Form Submit Prevented&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;282&quot; height=&quot;278&quot; src=&quot;https://www.mikewilson.dev/_astro/form-submit-invalid-pattern.BPcgw3ix_1Ojeui.webp&quot; &gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#wrapping-up&quot;&gt;Wrapping up&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As you can see, it’s fairly easy to add some basic form validation using Stimulus. With this technique, there is plenty more we can do, such as adding custom data-attributes for other custom validations, and styling the fields when they are invalid using the &lt;code&gt;input:invalid&lt;/code&gt; CSS selector.&lt;/p&gt;
&lt;p&gt;It’s worth keeping in mind that although it might seem easy enough to just add validation on the front end, you should always make sure to add the proper model validations on the server.&lt;/p&gt;
&lt;p&gt;To see an example, a bare-bones repository used in this example is available on Github at &lt;a href=&quot;https://github.com/mike1o1/stimulus-validation&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;mike1o1/stimulus-validation&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Dynamic Dialogs with Hotwire and Minimal Javascript</title><link>https://www.mikewilson.dev/posts/dynamic-dialogs-with-hotwire-and-minimal-javascript/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/dynamic-dialogs-with-hotwire-and-minimal-javascript/</guid><description>Dialogs are a staple of almost every modern web application. Fortunately, there are some newer techniques that we can employ to make modal dialogs less of a headache, and we can combine Hotwire, some minimal Javascript and some clever CSS to make some good looking dialogs.</description><pubDate>Wed, 04 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
.example-container {
    margin-bottom: 16px;
    padding: 8px 12px;
    background-color: #efefef;
}

.example-2 .modal-details .modal,
.example-3 .modal-details .modal {
    background-color: #ffffff;
    display: flex;
    flex-direction: column;
    width: 448px;
    position: fixed;
    margin: 10vh auto;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    z-index: 999;
    max-height: 80vh;
    max-width: 90vw;
}

.example-3 .modal-details[open] summary {
    cursor: default;
}

.example-3 .modal-details[open] summary:before {
    content: &apos;&apos;;
    display: block;
    width: 100vw;
    height: 100vh;
    background: black;
    position: fixed;
    top: 0;
    left: 0;
    opacity: 0.5;
    z-index: 99;
}

&lt;/style&gt;
&lt;p&gt;Dialogs are a staple of almost every modern web application. No matter how hard I try when designing an application,
there always seem to be scenarios where a dialog is the best option.&lt;/p&gt;
&lt;p&gt;Fortunately, there are some newer techniques that we can employ to make modal dialogs less of a headache, and we can
combine Hotwire, some minimal Javascript and some clever CSS to make some good looking dialogs. If we’re being extra
ambitious, we can even use &lt;a href=&quot;https://viewcomponent.org/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;View Components&lt;/a&gt; to make our markup less daunting.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;styling&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#styling&quot;&gt;Styling&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get us started, we’ll create some very bare-bones modal dialogs using nothing but CSS. We can get pretty far just
relying on base browser behavior and some clever CSS and pseudo-elements.&lt;/p&gt;
&lt;h3 id=&quot;summarydetails-element&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#summarydetails-element&quot;&gt;Summary/Details Element&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The main tool we’ll be relying on is the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Details&lt;/a&gt;
element, and the &lt;code&gt;summary&lt;/code&gt; element underneath. This is an element that can give us a native expandable content, which we
can use CSS to style into a modal dialog.&lt;/p&gt;
&lt;p&gt;The markup to create a detail/summary element will look like the below:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;modal-details&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;Click me&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;modal&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;Modal content.&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;details class=&amp;#x22;modal-details&amp;#x22;&gt;  &lt;summary&gt;Click me&lt;/summary&gt;  &lt;div class=&amp;#x22;modal&amp;#x22;&gt;Modal content.&lt;/div&gt;&lt;/details&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Without any styling, we’ll get a basic expandable element:&lt;/p&gt;
&lt;div class=&quot;example-container&quot;&gt;
&lt;details class=&quot;modal-details&quot;&gt;
    &lt;summary&gt;Click me&lt;/summary&gt;
    &lt;div class=&quot;modal&quot;&gt;
        Modal content.
    &lt;/div&gt;
&lt;/details&gt;
&lt;/div&gt;
&lt;h3 id=&quot;add-some-style&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#add-some-style&quot;&gt;Add Some Style&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We can add some styling using CSS to help things look not as ugly. The first thing we’ll want to do take our actual
modal and position it on the screen. There are many different approaches, but we’ll keep things simple for now and have
the modal be in a fixed position towards the top of the page.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;scss&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;.modal-details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// We&apos;ll just create a single modal for now, but we can optionally create different&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// size or positioned modals as well&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;.modal&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;background-color&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;#ffffff&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;flex-direction&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;column&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;448&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;px&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;position&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;fixed&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;margin&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;vh&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;auto&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;transform&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;translateX&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;-50&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;z-index&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;999&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;max-height&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;vh&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;max-width&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;90&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;vw&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;.modal-details {  // We&amp;#x27;ll just create a single modal for now, but we can optionally create different  // size or positioned modals as well  .modal {    background-color: #ffffff;    display: flex;    flex-direction: column;    width: 448px;    position: fixed;    margin: 10vh auto;    top: 0;    left: 50%;    transform: translateX(-50%);    z-index: 999;    max-height: 80vh;    max-width: 90vw;  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we were to take a look at the above snippet, things start to look a &lt;em&gt;little&lt;/em&gt; bit better. We’re getting there!&lt;/p&gt;
&lt;div class=&quot;example-container example-2&quot;&gt;
    &lt;details class=&quot;modal-details&quot;&gt;
        &lt;summary&gt;Click me&lt;/summary&gt;
        &lt;div class=&quot;modal&quot;&gt;
            Modal content.
        &lt;/div&gt;
    &lt;/details&gt;
&lt;/div&gt;
&lt;h4 id=&quot;adding-a-backdrop&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#adding-a-backdrop&quot;&gt;Adding A Backdrop&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When displaying a modal, it’s good to have some kind of backdrop to help the content standout a bit. Fortunately, we can
implement this using pure css. According to
the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#attr-open&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;documentation&lt;/a&gt; we can use the &lt;code&gt;open&lt;/code&gt;
attribute to know when the content is expanded or not. We can use this as a selector to add a pseudo-element to
the &lt;code&gt;summary&lt;/code&gt; element. Let’s see what that would look like:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;scss&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;.modal-details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;] {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;cursor&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--1:#6F42C1&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--0fs:italic&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic&quot;&gt;before&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;block&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;vw&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;vh&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;black&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;position&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;fixed&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;top&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;left&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;opacity&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;z-index&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;99&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;.modal-details {  &amp;#x26;[open] {    summary {      cursor: default;      &amp;#x26;:before {        content: &amp;#x22;&amp;#x22;;        display: block;        width: 100vw;        height: 100vh;        background: black;        position: fixed;        top: 0;        left: 0;        opacity: 0.5;        z-index: 99;      }    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What we’re doing is taking our modal-details element, and adding a &lt;code&gt;:before&lt;/code&gt; element to the &lt;code&gt;summary&lt;/code&gt; element, but only
when the &lt;code&gt;open&lt;/code&gt; attribute is present. The &lt;code&gt;open&lt;/code&gt; attribute is handled by the browser when the user clicks on the summary
element, so that part is handled for us.&lt;/p&gt;
&lt;p&gt;Let’s see what that looks like in practice.&lt;/p&gt;
&lt;div class=&quot;example-container example-3&quot;&gt;
    &lt;details class=&quot;modal-details&quot;&gt;
        &lt;summary&gt;Click me&lt;/summary&gt;
        &lt;div class=&quot;modal&quot;&gt;
            Modal content.
        &lt;/div&gt;
    &lt;/details&gt;
&lt;/div&gt;
&lt;p&gt;Wow - that’s actually not bad at all, considering we haven’t even written any Javascript yet. Since our backdrop is
added to the summary element, we get the added benefit of having the modal automatically close when user clicks on the
backdrop, as it’s the same as toggling the &lt;code&gt;summary&lt;/code&gt; element - nice!&lt;/p&gt;
&lt;h2 id=&quot;a-sprinkling-of-stimulus&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#a-sprinkling-of-stimulus&quot;&gt;A Sprinkling of Stimulus&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this section, we’ll add a little bit more functionality to our modal dialogs, such as loading turbo frames and adding
some nice touches such as closing the modal if the user hits escape.&lt;/p&gt;
&lt;p&gt;Let’s start with hitting escape to close the dialog. I’ll assume you’re familiar with Stimulus, but if not head over to
their &lt;a href=&quot;https://stimulus.hotwired.dev/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;docs&lt;/a&gt; to get familiar.&lt;/p&gt;
&lt;p&gt;First, we’ll want to create our controller. I use Typescript for all of my Stimulus controllers, so that’s what I’ll use
in the snippets below, but it should be pretty easy to translate it to normal Javascript.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Helper method since I don&apos;t want to have to cast this every time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// But this is necessary in Typescript in order for `open` to be a valid property&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;detailElement&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.element &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;HTMLDetailsElement&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;disconnect&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;toggleDialog&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.detailElement.open) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.detailElement.open &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.detailElement.open &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;closeOnEscape&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;KeyboardEvent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (event.key &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;Escape&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;event.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;stopPropagation&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;export default class extends Controller {  // Helper method since I don&amp;#x27;t want to have to cast this every time  // But this is necessary in Typescript in order for &amp;#x60;open&amp;#x60; to be a valid property  get detailElement() {    return this.element as HTMLDetailsElement;  }  disconnect() {    this.close();  }  public toggleDialog() {    if (this.detailElement.open) {      this.open();    } else {      this.close();    }  }  public open() {    this.detailElement.open = true;  }  public close() {    this.detailElement.open = false;  }  public closeOnEscape(event: KeyboardEvent) {    if (event.key === &amp;#x22;Escape&amp;#x22;) {      event.stopPropagation();      this.close();    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;In short, we’re adding a few public methods, for toggling the dialog, which can be helpful if we wanted to close or hide
the dialog programmatically outside of the &lt;code&gt;summary&lt;/code&gt; element, and we’re also adding a quick helper to close on escape.&lt;/p&gt;
&lt;p&gt;Let’s take the next step and actually wire-up our markup.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;details&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;modal-details&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-controller&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;modal&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-action&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;toggle-&gt;modal#toggleDialog keydown-&gt;modal#closeOnEscape&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;Click me!&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;modal&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Modal content.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-action&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;modal#close&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;Close me&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;details  class=&amp;#x22;modal-details&amp;#x22;  data-controller=&amp;#x22;modal&amp;#x22;  data-action=&amp;#x22;toggle-&gt;modal#toggleDialog keydown-&gt;modal#closeOnEscape&amp;#x22;&gt;  &lt;summary&gt;Click me!&lt;/summary&gt;  &lt;div class=&amp;#x22;modal&amp;#x22;&gt;    Modal content.    &lt;button type=&amp;#x22;button&amp;#x22; data-action=&amp;#x22;modal#close&amp;#x22;&gt;Close me&lt;/button&gt;  &lt;/div&gt;&lt;/details&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We’ve added an action on the &lt;code&gt;toggle&lt;/code&gt; event provided by the &lt;code&gt;details&lt;/code&gt; element. It doesn’t really do much yet, as the
browser already sets the &lt;code&gt;open&lt;/code&gt; state for us, but we’ll take advantage of that in the next section. You might notice we
also added a basic close button, which triggers the &lt;code&gt;#close&lt;/code&gt; action on the controller. This would allow us to add some
styling to get a nice “x” in the corner or an “Ok” button or something.&lt;/p&gt;
&lt;p&gt;We also trigger the &lt;code&gt;#closeOnEscape&lt;/code&gt; action, which simply inspects the event, looks at the key, and closes if the user
hit escape. Aside from accessibility nice-to-haves, this is a pretty complete solution for handling modal dialogs, with
very, very little lines of Javascript.&lt;/p&gt;
&lt;p&gt;In part two, we’ll go over how to incorporate &lt;a href=&quot;https://turbo.hotwired.dev/handbook/frames&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Turbo Frames&lt;/a&gt; to load content
dynamically, and then use a View Component to make the markup a little bit more reusable.&lt;/p&gt;</content:encoded></item><item><title>Ember CLI - Easily toggle between mocks and live server</title><link>https://www.mikewilson.dev/posts/ember-cli-easily-toggle-between-mocks-and-live-server/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/ember-cli-easily-toggle-between-mocks-and-live-server/</guid><description>How to modify Ember CLI&apos;s generated server code to easily toggle between http-mocks and a live development server using environment arguments.</description><pubDate>Mon, 02 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the great things about &lt;a href=&quot;http://www.emberjs.com&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Ember&lt;/a&gt; is how productive I feel, and how it allows me to focus solely on the front end. &lt;a href=&quot;http://www.ember-cli.com/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Ember CLI&lt;/a&gt; makes this even faster, as it makes it super easy to create http-mocks of the backend, which allows me to continue working on the front-end while the Web API portion of the application is still being developed or finalized. If the same team member is working on both, sometimes an approach of starting with the UI also helps vet out the requirements for the Web API as well.&lt;/p&gt;
&lt;p&gt;However, I was recently looking for an easy way to toggle between using my mocks and using a live development server to validate everything. In addition, I sometimes work completely offline and want to solely rely on my http mocks. Fortunately, with some tweaks to the generated code from Ember CLI, this was very easy to do.&lt;/p&gt;
&lt;p&gt;Creating an http-mock with Ember CLI is very easy to do. All it takes is a call to&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame is-terminal&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;ember&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;generate&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;http-mock&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;$ ember generate http-mock user&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This will create a few things the first time it’s run. The first, is it creates a &lt;code&gt;server&lt;/code&gt; folder under your application, an &lt;code&gt;index.js&lt;/code&gt; file under the &lt;code&gt;server&lt;/code&gt; folder which sets up the server, as well as a file for the mock API you wish to stub out.&lt;/p&gt;
&lt;p&gt;However, one of the problems with the Ember CLI generated code is that out of the box, it wires up both http-proxy’s as well as http-mocks.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; globSync &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;glob&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;).sync;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; mocks &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;globSync&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;./mocks/**/*.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;, { cwd&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; __dirname }).&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(require);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; proxies &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;globSync&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;./proxies/**/*.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;, { cwd&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; __dirname }).&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(require);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Log proxy requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; morgan &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;morgan&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;morgan&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;dev&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;mocks.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(app);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;proxies.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(app);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;module.exports = function (app) {  var globSync = require(&amp;#x22;glob&amp;#x22;).sync;  var mocks = globSync(&amp;#x22;./mocks/**/*.js&amp;#x22;, { cwd: __dirname }).map(require);  var proxies = globSync(&amp;#x22;./proxies/**/*.js&amp;#x22;, { cwd: __dirname }).map(require);  // Log proxy requests  var morgan = require(&amp;#x22;morgan&amp;#x22;);  app.use(morgan(&amp;#x22;dev&amp;#x22;));  mocks.forEach(function (route) {    route(app);  });  proxies.forEach(function (route) {    route(app);  });};&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This can be problematic if you want to use alternate back and forth between a mock and your dev server, or if you want to exclusively use mocks (for example if you don’t have access to your dev server), or exclusively use your proxy without blowing away your mocks.&lt;/p&gt;
&lt;h3 id=&quot;diving-into-the-source&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#diving-into-the-source&quot;&gt;Diving into the source&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An easy, but hidden, way of doing this is to modify the generated and get access to the options which are passed in from the &lt;code&gt;ember server&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;If you dive into the ember-cli source, specifically into &lt;code&gt;express-server.js&lt;/code&gt;, you’ll see that the &lt;code&gt;processAppMiddlewares&lt;/code&gt; function is where the server is being initiated, and that two properties are passed in: &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;options&lt;/code&gt;. The &lt;code&gt;options&lt;/code&gt; object is what contains a reference to the environment the server is started with.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#6F42C1&quot;&gt;processAppMiddlewares&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.project.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;has&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.serverRoot)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; server &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.project.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.serverRoot);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;typeof&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; server &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--0fw:bold;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;TypeError&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;ember-cli expected ./server/index.js to be the entry for your mock or proxy server&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.app, options);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;processAppMiddlewares: function(options) {    if (this.project.has(this.serverRoot)) {        var server = this.project.require(this.serverRoot);        if (typeof server !== &amp;#x27;function&amp;#x27;) {            throw new TypeError(&amp;#x27;ember-cli expected ./server/index.js to be the entry for your mock or proxy server&amp;#x27;);        }        server(this.app, options);    }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the &lt;code&gt;ember server&lt;/code&gt; commandline task is called, you can pass in any type of arguments you want for the environment, and now you can access that environment on the &lt;code&gt;options.environment&lt;/code&gt; within your &lt;code&gt;server/index.js&lt;/code&gt; method! This allows you to easily toggle back and forth between using mocks or proxies. This can also be really helpful if you want to do development against a live server, but only hit a mock when running &lt;code&gt;ember test&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;See the example below, where if I want to use mocks when &lt;code&gt;environment=offline&lt;/code&gt;, otherwise use a proxy to my dev server.&lt;/p&gt;
&lt;p&gt;All I then have to do is call &lt;code&gt;ember server --environment=offline&lt;/code&gt; and I can use my mocks.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; globSync &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;glob&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;).sync;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; mocks, proxies;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (options.environment &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;offline&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;mocks &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;globSync&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;./mocks/**/*.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;cwd&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; __dirname,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(require);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;proxies &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;globSync&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;./proxies/**/*.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;cwd&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; __dirname,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(require);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Log proxy requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; morgan &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;morgan&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;morgan&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;dev&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (mocks) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;mocks.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(app);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (proxies) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;proxies.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;route&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(app);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;module.exports = function (app, options) {  var globSync = require(&amp;#x22;glob&amp;#x22;).sync;  var mocks, proxies;  if (options.environment === &amp;#x22;offline&amp;#x22;) {    mocks = globSync(&amp;#x22;./mocks/**/*.js&amp;#x22;, {      cwd: __dirname,    }).map(require);  } else {    proxies = globSync(&amp;#x22;./proxies/**/*.js&amp;#x22;, {      cwd: __dirname,    }).map(require);  }  // Log proxy requests  var morgan = require(&amp;#x22;morgan&amp;#x22;);  app.use(morgan(&amp;#x22;dev&amp;#x22;));  if (mocks) {    mocks.forEach(function (route) {      route(app);    });  }  if (proxies) {    proxies.forEach(function (route) {      route(app);    });  }};&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Hopefully this helps those of you who might do development against a server not running on your machine, but still want offline access, or simply want a way to easily toggle between your mocks and a live server.&lt;/p&gt;</content:encoded></item><item><title>Preventing Minitest Focus During CI</title><link>https://www.mikewilson.dev/posts/preventing-minitest-focus-during-ci/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/preventing-minitest-focus-during-ci/</guid><description>How to prevent accidentally leaving a Minitest focus statement in a pull request by patching the method to throw an exception in CI environments.</description><pubDate>Fri, 26 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I really love using &lt;a href=&quot;https://github.com/seattlerb/minitest&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Minitest&lt;/a&gt; along with &lt;a href=&quot;https://github.com/seattlerb/minitest-focus&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Minitest Focus&lt;/a&gt; during the development process with Rails.&lt;/p&gt;
&lt;p&gt;All it takes is adding &lt;code&gt;focus&lt;/code&gt; before a test (or multiple tests), and running &lt;code&gt;rails test&lt;/code&gt; will only run those tests. This makes it very handy when working through a specific issue and continually running only those selected tests.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;test_helper&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MyControllerTest&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#6F42C1&quot;&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--0fs:italic&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic&quot;&gt;IntegrationTest&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;focus&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;should render index page&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Test goes here.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Only this test will run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;should render show page&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Test goes here.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# This will get skipped&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;require &amp;#x22;test_helper&amp;#x22;class MyControllerTest &lt; ActionDispatch::IntegrationTest    focus    test &amp;#x22;should render index page&amp;#x22; do        # Test goes here.        # Only this test will run    end    test &amp;#x22;should render show page&amp;#x22; do        # Test goes here.        # This will get skipped    endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The way &lt;code&gt;minitest-focus&lt;/code&gt; works is that it basically changes the arguments passed to minitest to only include tests defined after the &lt;code&gt;focus&lt;/code&gt; statement. Very helpful!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;However, one issue that I’ve run across during development of &lt;a href=&quot;https://www.kwikcal.com&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Kwikcal&lt;/a&gt; is accidentally leaving a &lt;code&gt;focus&lt;/code&gt; statement in a PR. It’s a small line, so easy to miss during code reviews. When this happens, your CI integration might give a false positive as your CI might only execute the focused tests!&lt;/p&gt;
&lt;p&gt;Aside from catching this during a code review, a simple way to prevent accidentally leaving &lt;code&gt;focus&lt;/code&gt; statements in a pull request is to patch the method to throw an exception while in a CI environment.&lt;/p&gt;
&lt;p&gt;To do this, I created a file named &lt;code&gt;prevent_focus_in_ci.rb&lt;/code&gt; and placed it in my &lt;code&gt;test/support&lt;/code&gt; folder, as these files are automatically loaded in my test helper.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;CI&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Or &quot;CIRCLECI&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#6F42C1&quot;&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;Minitest&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;Test&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;self.focus&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;raise&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;StandardError&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;FOCUS CALLED IN ERROR: &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#032F62&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;if ENV[&amp;#x22;CI&amp;#x22;] # Or &amp;#x22;CIRCLECI&amp;#x22;    class Minitest::Test        def self.focus            raise StandardError.new(&amp;#x22;FOCUS CALLED IN ERROR: #{self.name}&amp;#x22;)        end    endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With this little snippet you’ll get notified through a failing test suite if this method is ever left over in any of your test files. The raised error will let you know the test file it was added to.&lt;/p&gt;
&lt;p&gt;Below is example output from &lt;a href=&quot;https://circleci.com/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Circle CI&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame is-terminal&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;1:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;/home/circleci/project/test/models/example_test.rb:61:in&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;&amp;#x3C;class:ExampleTest&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;/home/circleci/project/test/support/prevent_focus_in_ci.rb:8:in `focus&apos;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt; FOCUS ADDED IN ERROR: [ExampleTest] (&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;StandardError&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;1: from /home/circleci/project/test/models/example_test.rb:61:in &amp;#x60;&lt;class:ExampleTest&gt;&amp;#x27;/home/circleci/project/test/support/prevent_focus_in_ci.rb:8:in &amp;#x60;focus&amp;#x27;: FOCUS ADDED IN ERROR: [ExampleTest] (StandardError)&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Very handy for making sure your entire test suite runs on CI, while still being able to use the very helpful &lt;code&gt;focus&lt;/code&gt; method during development.&lt;/p&gt;</content:encoded></item><item><title>Rails Vanity URL&apos;s With Route Constraints</title><link>https://www.mikewilson.dev/posts/rails-vanity-urls-with-route-constraints/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/rails-vanity-urls-with-route-constraints/</guid><description>Looking to create a root-level vanity URL, while still having a catch-all route or using high_voltage gem? Read on to learn how to use Route Constraints in Rails to achieve this.</description><pubDate>Sat, 27 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A vanity URL is a great way to get a cleaner URL for your users, and is also a great way to improve SEO. Providing vanity URL’s to your users if they have a public facing page is also a great feature to add to give your users more personalization.&lt;/p&gt;
&lt;p&gt;An example of a vanity URL would be linking to a username, instead of a user ID to see a users posts:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://www.example.com/users/2348&lt;/code&gt; - This is an example of a non-vanity URL
&lt;code&gt;https://www.example.com/users/john-smith&lt;/code&gt; - This is a vanity URL and looks much friendlier&lt;/p&gt;
&lt;p&gt;There are plenty of articles on how to add vanity URL’s, or slugs, with Rails, with the easiest method to be using a gem like &lt;a href=&quot;https://github.com/norman/friendly_id&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;friendly_id&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What happens when you want to make the URL be on your root page, while still having a catch-all route, or use a gem such as &lt;a href=&quot;https://github.com/thoughtbot/high_voltage&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;high_voltage&lt;/a&gt;? It’s still doable, if not a little tricky.&lt;/p&gt;
&lt;h4 id=&quot;setting-up-the-initial-route&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#setting-up-the-initial-route&quot;&gt;Setting up the initial route&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The first thing you’ll want to do is add a route in your &lt;code&gt;routes.rb&lt;/code&gt; to specify the resource, and provide a root level path. This will tell rails that anything to your root level route should try and load the user resource.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Place at the end of your routes.rb file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;resources &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;path:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;    # Place at the end of your routes.rb file    resources :users, path: &amp;#x22;&amp;#x22;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This should setup a URL, such as &lt;code&gt;example.com/john-smith&lt;/code&gt;, where &lt;code&gt;john-smith&lt;/code&gt; is the id passed to the route. Now, whenever somebody visits &lt;code&gt;example.com/john-smith&lt;/code&gt;, it will hit your &lt;code&gt;UsersController&lt;/code&gt; where you use a friendly finder method to find the user details.&lt;/p&gt;
&lt;p&gt;What if we have a catch-all route setup further down our routing file to provide better error pages, or we use a gem such as &lt;code&gt;high_voltage&lt;/code&gt; for our static page routing?&lt;/p&gt;
&lt;p&gt;Unfortunately, the &lt;code&gt;users&lt;/code&gt; route will kick in first and we’ll never be able to navigate to our static pages or other catch-all routes. This is where Route Constraints come in.&lt;/p&gt;
&lt;h4 id=&quot;route-constraints&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#route-constraints&quot;&gt;Route Constraints&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Routing constraints can be a very powerful tool to give you flexibility over your routes. A &lt;a href=&quot;https://guides.rubyonrails.org/routing.html#advanced-constraints&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;route constraint&lt;/a&gt; is an object that responds to &lt;code&gt;matches?&lt;/code&gt; and tells Rails whether the route matched or not.&lt;/p&gt;
&lt;p&gt;We can use a Route Constraint on our &lt;code&gt;users&lt;/code&gt; route, so that if the &lt;code&gt;:id&lt;/code&gt; from the route doesn’t match a username in our system, we return false and Rails moves on to the next route.&lt;/p&gt;
&lt;p&gt;Let’s see it in action:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;app/lib/constraints/username_route_constrainer.rb&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Constraints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;UsernameRouteConstrainer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;matches?&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Get our username from the route params&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; request.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Check if this name exists for any users&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;slug:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; username).&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;any?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;module Constraints    class UsernameRouteConstrainer        def matches?(request)            # Get our username from the route params            username = request.params[:id]            # Check if this name exists for any users            User.where(slug: username).any?        end    endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is a pretty straight forward class. We pull the username out of the request parameters, and then look to see if the user exists in the system.&lt;/p&gt;
&lt;p&gt;Now we’ll want to modify our routing configuration to tell routes to use this new route constraint:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;constraints&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;Constraints&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;UsernameRouteConstrainer&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Same route as before, only within the constraints block&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;resources &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;path:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;constraints(Constraints::UsernameRouteConstrainer.new) do    # Same route as before, only within the constraints block    resources :users, path: &amp;#x22;&amp;#x22;end&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;small&gt;Note: You might need to restart spring to pick up files in the new &lt;code&gt;constraints&lt;/code&gt; folder.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Now anytime we try and load our page, such as &lt;code&gt;example.com/john-smith&lt;/code&gt;, we’ll see Rails make a request to see if &lt;code&gt;john-smith&lt;/code&gt; is a valid slug in our Users table.&lt;/p&gt;
&lt;h4 id=&quot;performance-improvements&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#performance-improvements&quot;&gt;Performance Improvements&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There are a few things that we can do to make our route constrainer even better. If we are using &lt;code&gt;high_voltage&lt;/code&gt; for our static pages, we might notice that Rails makes a User lookup each time, even if we are loading a static page. This happens because Rails checks our route, sees that it fails, and then the &lt;code&gt;high_voltage&lt;/code&gt; Engine or catch-all route is then triggered.&lt;/p&gt;
&lt;p&gt;Can we try and tell our route constrainer to automatically fail if we’re loading a known static page? Yes we can!&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;high_voltage&lt;/code&gt; gem provides some handy tools, one of which is a list of all of the known static pages that &lt;code&gt;high_voltage&lt;/code&gt; is going to manage. This is available in the &lt;code&gt;HighVoltage::page_ids&lt;/code&gt; method, which returns an array of pages.&lt;/p&gt;
&lt;p&gt;All we have to do in our route constrainer is add a check to see if that &lt;code&gt;/:id&lt;/code&gt; param exists in those page ids, and fail if it does. This helps, as Rails will no longer do a needless database lookup, that we know will fail.&lt;/p&gt;
&lt;p&gt;Here is what that method might look like:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;is_static_page?&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic&quot;&gt;param_id&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;HighVoltage&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;page_ids&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;include?&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(param_id)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;def is_static_page?(param_id)    HighVoltage::page_ids.include?(param_id)end&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This method will check the param and see if it is a &lt;code&gt;high_voltage&lt;/code&gt; page id. Let’s see what the full route constraint looks like now:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;app/lib/constraints/username_route_constrainer.rb&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Constraints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;UsernameRouteConstrainer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;matches?&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Get our username from the route params&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; request.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; param_id.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;blank?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;is_static_page?&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(username)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Check if this name exists for any users&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;slug:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; username).&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;any?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;private&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;is_static_page?&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic&quot;&gt;param_id&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;                &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;HighVoltage&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;page_ids&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;include?&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(param_id)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;module Constraints    class UsernameRouteConstrainer        def matches?(request)            # Get our username from the route params            username = request.params[:id]            return false if param_id.blank?            return false if is_static_page?(username)            # Check if this name exists for any users            User.where(slug: username).any?        end        private            def is_static_page?(param_id)                HighVoltage::page_ids.include?(param_id)            end    endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Much better. Now we get the benefits of root level vanity URL’s for our users, as well as the ease and flexibility of other catch-all routes further down the routing table. We’ve also added a few short-circuits to our route constraint, to improve performance and remove any unnecessary database look ups.&lt;/p&gt;
&lt;h4 id=&quot;example&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#example&quot;&gt;Example&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For full code for this example, see the following repository on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mike1o1/route-constraints-example&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;mike1o1/route-constraints-example&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Redirecting to static webpacker Content With Rails</title><link>https://www.mikewilson.dev/posts/redirecting-to-static-webpacker-content-with-rails/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/redirecting-to-static-webpacker-content-with-rails/</guid><description>With the release of Rails 6, webpacker is the new default compilation pipe for Javascript assets. Learn how to create a standalone widget file which redirects to webpacker compiled assets.</description><pubDate>Wed, 22 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With the release of Rails 6, &lt;a href=&quot;https://github.com/rails/webpacker&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;webpacker&lt;/a&gt; will be the new default compilation pipeline for Javascript assets. This is a great thing once you wrap your head around how to use webpack and webpacker.&lt;/p&gt;
&lt;p&gt;One of the benefits of webpacker, is like sprockets, the automatic compilation and fingerprinting of assets for production builds. Fingerprinting is very handy as it will automatically break any browser caches when new versions of that asset are compiled.&lt;/p&gt;
&lt;p&gt;What if you have a Javascript snippet that you want others to use dynamically, such as a widget or html snippet? The fingerprinting of assets then makes it very difficult, as the filename will keep changing over and over again.&lt;/p&gt;
&lt;p&gt;Fortunately with a combination of a Rails route and diving into the &lt;code&gt;webpacker&lt;/code&gt; gem, this can be accomplished fairly easily using redirects.&lt;/p&gt;
&lt;h3 id=&quot;rails-routing&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#rails-routing&quot;&gt;Rails Routing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s say you have a Javascript widget that you want your customers to embed on their site. You want something easy to remember, such as &lt;code&gt;https://www.example.com/external/widget.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let’s first setup our routes:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;config/routes.rb&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;get &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:widget&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;to:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;widgets#show&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;path:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;external/widget&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;get :widget, to: &amp;#x22;widgets#show&amp;#x22;, path: &amp;#x22;external/widget&amp;#x22;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Our controller needs to then respond to the request, find the webpacker asset, and then respond with a redirect to the fingerprinted asset. Let’s look at the code:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;WidgetsController&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;ApplicationController&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;ActionView&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;Helpers&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;AssetUrlHelper&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#005CC5&quot;&gt;Webpacker&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;Helper&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;protect_from_forgery &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;except:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:show&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;show&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;respond_to &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; |format|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#005CC5&quot;&gt;format&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;js&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { redirect_to widget_javascript_source }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;private&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;widget_javascript_source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;asset_pack_url&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;widget.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;class WidgetsController &lt; ApplicationController    include ActionView::Helpers::AssetUrlHelper    include Webpacker::Helper    protect_from_forgery except: :show    def show        respond_to do |format|            format.js { redirect_to widget_javascript_source }        end    end    private        def widget_javascript_source            asset_pack_url(&amp;#x22;widget.js&amp;#x22;)        endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Most of this is fairly straight forward. The first thing we want to do is tell Rails not to worry about protecting from anti-forgery requests. Since this will be used from external applications, it’s not something we’re worried about.&lt;/p&gt;
&lt;p&gt;The real magic comes in our &lt;code&gt;widget_javascript_source&lt;/code&gt; method, which refers to the &lt;code&gt;asset_pack_url&lt;/code&gt; that is included in the &lt;code&gt;Webpacker::Helper&lt;/code&gt; module.&lt;/p&gt;
&lt;h3 id=&quot;what-does-webpacker-actually-do&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#what-does-webpacker-actually-do&quot;&gt;What does webpacker actually do?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To figure out what &lt;code&gt;asset_pack_url&lt;/code&gt; does, it’s good to know what &lt;code&gt;webpacker&lt;/code&gt; actually does for us behind the scenes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;webpacker&lt;/code&gt; gem is a wrapper around &lt;code&gt;webpack&lt;/code&gt;, and hides away a lot of the complexity of &lt;code&gt;webpack&lt;/code&gt;. For our use case, the most important thing is compiling our assets. Instead of using &lt;code&gt;sprockets&lt;/code&gt;, we can now rely on &lt;code&gt;webpack&lt;/code&gt; to do all of our assets, and with that, we can hook into the broader webpack ecosystem. Part of that includes asset compilation if we’re using Typescript, asset transpiling using a tool such as babel, and minification and fingerprinting to optimize the assets we are serving.&lt;/p&gt;
&lt;p&gt;One thing that &lt;code&gt;webpacker&lt;/code&gt; does for us when it compiles all of these assets for us is it creates a &lt;code&gt;manifest.json&lt;/code&gt; file with a list of entrypoints and a mapping of the compiled asset name. This is what the new &lt;code&gt;javascript_pack_tag&lt;/code&gt; ends up doing for us. We pass in the friendly name, and it uses that manifest file to find the fingerprinted asset name.&lt;/p&gt;
&lt;p&gt;If we look at the &lt;code&gt;manifest.json&lt;/code&gt; file in the &lt;code&gt;public/packs&lt;/code&gt; folder, we’ll see an example&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;application.js&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/application-feff836d0a539cc07796.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;application.js.map&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/application-feff836d0a539cc07796.js.map&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;widget.js&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/widget-feff836d0a539cc07796.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;widget.js.map&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/widget-feff836d0a539cc07796.js.map&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;entrypoints&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;js&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/application-feff836d0a539cc07796.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;js.map&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/application-feff836d0a539cc07796.js.map&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;widget&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;js&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/widget-feff836d0a539cc07796.js&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;js.map&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;/packs/widget-feff836d0a539cc07796.js.map&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;#x22;application.js&amp;#x22;: &amp;#x22;/packs/application-feff836d0a539cc07796.js&amp;#x22;,  &amp;#x22;application.js.map&amp;#x22;: &amp;#x22;/packs/application-feff836d0a539cc07796.js.map&amp;#x22;,  &amp;#x22;widget.js&amp;#x22;: &amp;#x22;/packs/widget-feff836d0a539cc07796.js&amp;#x22;,  &amp;#x22;widget.js.map&amp;#x22;: &amp;#x22;/packs/widget-feff836d0a539cc07796.js.map&amp;#x22;,  &amp;#x22;entrypoints&amp;#x22;: {    &amp;#x22;application&amp;#x22;: {      &amp;#x22;js&amp;#x22;: [&amp;#x22;/packs/application-feff836d0a539cc07796.js&amp;#x22;],      &amp;#x22;js.map&amp;#x22;: [&amp;#x22;/packs/application-feff836d0a539cc07796.js.map&amp;#x22;]    },    &amp;#x22;widget&amp;#x22;: {      &amp;#x22;js&amp;#x22;: [&amp;#x22;/packs/widget-feff836d0a539cc07796.js&amp;#x22;],      &amp;#x22;js.map&amp;#x22;: [&amp;#x22;/packs/widget-feff836d0a539cc07796.js.map&amp;#x22;]    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What webpacker is doing is reading the content of our manifest file, looking up the friendly name of our asset (widget.js), and then returns to us the properly compiled, minified and fingerprinted asset.&lt;/p&gt;
&lt;p&gt;So, all we need to ask webpacker for us the name of our compiled &lt;code&gt;widget.js&lt;/code&gt; and then redirect from there!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;After looking through some of the internals of webpacker gem, we can see just how easy it is to hook into the compiled assets from our Rails controllers. This allows us to continue to modify and enhance our external &lt;code&gt;widget.js&lt;/code&gt; file, and even work on it in a language such as &lt;a href=&quot;https://www.typescriptlang.org/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Typescript&lt;/a&gt;, while still providing a single, easy to use snippet for others to embed in their own websites.&lt;/p&gt;</content:encoded></item><item><title>Supporting refresh tokens with Ash Authentication</title><link>https://www.mikewilson.dev/posts/refresh-tokens-with-ash-authentication/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/refresh-tokens-with-ash-authentication/</guid><description>Explore how to use Ash Authentication&apos;s existing password strategy to generate access tokens, and expand on it to support generating and exchanging access token and refresh tokens as well.</description><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I’ve been working a lot with Elixir and have migrated a recent side project to use the &lt;a href=&quot;https://ash-hq.org/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Ash Framework&lt;/a&gt;, which gives fantastic support for modeling your domain and deriving everything you need from it. Ash Framework provides support for Authentication via &lt;a href=&quot;https://hexdocs.pm/ash_authentication/readme.html&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Ash Authentication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ash Authentication is a pretty modular framework and comes with various strategies, such as magic link or password authentication. For the project I’m working on, I’m powering the backend of a mobile-first application, and I wanted to use &lt;a href=&quot;https://hexdocs.pm/ash_authentication/password.html&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;password&lt;/a&gt; authentication. I won’t go through the whole setup here, as the docs cover it much better than I can, but eventually you’ll end up with a user resource that looks something like this:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Accounts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;otp_app:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;domain:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Accounts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;extensions:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;data_layer:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshPostgres&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;DataLayer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;authentication &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;tokens &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;enabled? &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;strategies &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;password &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;identity_field &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:email&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;actions &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;read &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:sign_in_with_password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;argument &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:email&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ci_string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;argument &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;sensitive?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;prepare &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Strategy&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;SignInPreparation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;metadata &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;create &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:register_with_password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;defmodule MyApp.Accounts.User do  use Ash.Resource,    otp_app: :my_app,    domain: MyApp.Accounts,    extensions: [AshAuthentication],    data_layer: AshPostgres.DataLayer  authentication do    tokens do      enabled? true      # ...    end    strategies do      password :password do        identity_field :email        # ...      end    end  end  actions do    read :sign_in_with_password do      # ...      argument :email, :ci_string, allow_nil?: false      argument :password, :string, allow_nil?: false, sensitive?: true      prepare AshAuthentication.Strategy.Password.SignInPreparation      metadata :token, :string, allow_nil?: false    end    create :register_with_password do      # ...    end  endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There is a lot more setup, but this is the gist of what we’ll be working on. With this setup, we get great support for password-based authentication out of the box. For a GraphQL endpoint, this gives us token-based authentication that we can use to authenticate access to the API’s. Each time a request is made to the &lt;code&gt;sign_in_with_password&lt;/code&gt; action, a &lt;code&gt;Token&lt;/code&gt; is generated and returned with the response.&lt;/p&gt;
&lt;p&gt;If we were to set up GraphQL (using &lt;a href=&quot;https://hexdocs.pm/ash_graphql/readme.html&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Ash GraphQL&lt;/a&gt;), then we’d login with a query like this:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;graphql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;LoginWithPassword&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;signInWithPassword&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;test@example.com&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;query LoginWithPassword {  signInWithPassword(email: &amp;#x22;test@example.com&amp;#x22;, password: &amp;#x22;password&amp;#x22;) {    token  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;In our response, we’d get an access token. We can then add it to any outgoing request by setting the &lt;code&gt;Authorization&lt;/code&gt; header with the value of &lt;code&gt;Bearer #{token}&lt;/code&gt;. Our plug would decode and verify the token, ensure it’s not expired, parse out the user and confirm access.&lt;/p&gt;
&lt;p&gt;This works great on the web, but it’s not the best experience on mobile as the token only lasts for 14 days, meaning the user would have to reauthenticate themself after the token expires. It’s much more common to use Access and &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6749#section-1.5&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Refresh Tokens&lt;/a&gt; in mobile apps. In short, after authenticating, we would return a short-lived access token, similar to what we do today, but also a &lt;em&gt;refresh&lt;/em&gt; token that the client can exchange for another access token. In this post, we’ll explore a way to do that with Ash, while still keeping a lot of the really nice benefits that we get from Ash Authentication, such as the ability to store and revoke tokens.&lt;/p&gt;
&lt;h3 id=&quot;additional-actions&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#additional-actions&quot;&gt;Additional Actions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To implement refresh tokens, we need to augment the existing &lt;code&gt;:sign_in_with_password&lt;/code&gt; action to generate a refresh token, and we will also need to add a new action to exchange the refresh token for a new access token.&lt;/p&gt;
&lt;p&gt;The lifecycle will be:&lt;/p&gt;
&lt;img alt=&quot;Authentication Flow&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;3809&quot; height=&quot;3840&quot; src=&quot;https://www.mikewilson.dev/_astro/auth_flow.Bd6CkrBU_Z1KDneg.webp&quot; &gt;
&lt;p&gt;To get started, we’ll take the first step of generating the access token when signing in with a password.&lt;/p&gt;
&lt;h4 id=&quot;generating-access-tokens&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#generating-access-tokens&quot;&gt;Generating Access Tokens&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Fortunately, Ash Authentication password strategy already does a lot of the hard work for us by validating the user and password in a &lt;code&gt;preparation&lt;/code&gt;, named &lt;code&gt;AshAuthentication.Strategy.Password.SignInPreparation&lt;/code&gt;. This preparation does two jobs, before and after the query executes. The after-job is where the password is verified and the initial access token is generated. Fortunately, there’s nothing we need to do here!&lt;/p&gt;
&lt;p&gt;However, if we want to generate an access token, we’ll want to do our own &lt;code&gt;preparation&lt;/code&gt;, which we’ll name &lt;code&gt;GenerateRefreshToken&lt;/code&gt;. We’ll want to take a similar approach to the built-in preparation, which is to add an &lt;code&gt;after_action&lt;/code&gt; hook to generate a refresh token. Tokens in Ash can be generated by supplying a user, a map of additional claims and an optional set of options (such as expiration time).&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;token_for_user&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, %{&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;extra_claim&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hooray&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}, options)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Returns&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, token, claims}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;AshAuthentication.Jwt.token_for_user(user, %{&amp;#x22;extra_claim&amp;#x22; =&gt; &amp;#x22;hooray&amp;#x22;}, options)# Returns{:ok, token, claims}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;So, let’s get started by writing our own preparation.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Accounts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Preparations&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;GenerateRefreshToken&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.{&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;prepare&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, options, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;query&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;after_action&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;1, &amp;#x26;2, context))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, [user], context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, [user]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, result, &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, result}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;defmodule MyApp.Accounts.User.Preparations.GenerateRefreshToken do  require Ash.Query  alias Ash.{Query, Resource}  def prepare(query, options, context) do    query    |&gt; Query.after_action(&amp;#x26;generate_refresh_token(&amp;#x26;1, &amp;#x26;2, context))  end  defp generate_refresh_token(query, [user], context) do    {:ok, [user]}  end  defp generate_refresh_token(_query, result, _context) do    {:ok, result}  endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We can then use this preparation in our action by adding it &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;SignInPreparation&lt;/code&gt; action. New installs of Ash (and the default in Ash 4.0) will execute &lt;code&gt;after_action&lt;/code&gt; hooks in the order that they’re declared. To ensure you get that behavior, you’ll want to make sure you’re on Ash 3.4.69 or higher and have &lt;code&gt;config :ash, :read_action_after_action_hooks_in_order?, true&lt;/code&gt; set in your configuration. Otherwise, if you’re on an older version of Ash 3.0, you’ll want to make sure your new preparation is declared &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;SignInPreparation&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;So, our action should now look something like:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;read &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:sign_in_with_password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;get? &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;argument &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:email&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ci_string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;argument &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;prepare &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Strategy&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;SignInPreparation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;prepare &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Accounts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Preparations&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;GenerateRefreshToken&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;metadata &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;read :sign_in_with_password do  get? true  argument :email, :ci_string, allow_nil?: false  argument :password, :string, allow_nil?: false  prepare AshAuthentication.Strategy.Password.SignInPreparation  prepare MyApp.Accounts.User.Preparations.GenerateRefreshToken  metadata :token, :string, allow_nil?: falseend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The reason we want our &lt;code&gt;after_action&lt;/code&gt; hook to execute &lt;em&gt;after&lt;/em&gt; the built-in one, is we want to see if there is an access token already generated for the user and placed on that users metadata. If there is, we know that authentication was successful.&lt;/p&gt;
&lt;p&gt;Let’s see that in action. Let’s go back to our preparation, specifically our first pattern match:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# The second argument might look a bit weird, but it&apos;s because Ash queries always&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# return lists, even if we&apos;re only expecting one user. So, we pattern match on that&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# and extract the user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, [user], context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Get the token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;token &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;get_metadata&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# If token is not nil, then we know that Ash Authentication&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# generated an access token for us. Thanks!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; token &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;token &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;not&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;is_nil&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(token) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;-&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Generate refresh token and add to user metadata&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, refresh_token, &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_claims&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, context)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;user &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; user &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;put_metadata&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, refresh_token)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Return a list of users, as that is what Ash.Query is expecting&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, [user]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;other &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;-&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# No token was generated, we should return an error&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;Unable to retrieve token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;opts &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;to_opts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Keyword&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token_lifetime&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:days&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;token_for_user&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, %{&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;purpose&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}, opts)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;# The second argument might look a bit weird, but it&amp;#x27;s because Ash queries always# return lists, even if we&amp;#x27;re only expecting one user. So, we pattern match on that# and extract the userdefp generate_refresh_token(query, [user], context) do  # Get the token  token = Ash.Resource.get_metadata(user, :token)  # If token is not nil, then we know that Ash Authentication  # generated an access token for us. Thanks!  case token do    token when not is_nil(token) -&gt;      # Generate refresh token and add to user metadata      {:ok, refresh_token, _claims} = generate_token(user, context)      user = user |&gt; Ash.Resource.put_metadata(:refresh_token, refresh_token)      # Return a list of users, as that is what Ash.Query is expecting      {:ok, [user]}    other -&gt;      # No token was generated, we should return an error      {:error, &amp;#x22;Unable to retrieve token&amp;#x22;}  endenddefp generate_token(user, context) do  opts = Ash.Context.to_opts(context) |&gt; Keyword.put(:token_lifetime, {30, :days})  AshAuthentication.Jwt.token_for_user(user, %{&amp;#x22;purpose&amp;#x22; =&gt; &amp;#x22;refresh_token&amp;#x22;}, opts)end&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Note here that we’re adding some metadata to the user resource. In order to actually read that in our action, we need to add it as a metadata field on the action itself. Let’s modify our action to add the new refresh_token field.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;read &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:sign_in_with_password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Existing code...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;metadata &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Add the refresh token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;metadata &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;read :sign_in_with_password do  # Existing code...  metadata :token, :string, allow_nil?: false  # Add the refresh token  metadata :refresh_token, :string, allow_nil?: falseend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We’ll also want to modify our &lt;code&gt;tokens&lt;/code&gt; block to update the token lifetime.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;authentication &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;tokens &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# existing config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;token_lifetime {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:hours&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;authentication do  ...  tokens do    # existing config    token_lifetime {1, :hours}  endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With this change, our GraphQL schema should update to add the new refresh token to our query:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;graphql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;LoginWithPassword&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;signInWithPassword&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;test@example.com&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;password&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;refreshToken&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;query LoginWithPassword {  signInWithPassword(email: &amp;#x22;test@example.com&amp;#x22;, password: &amp;#x22;password&amp;#x22;) {    token    refreshToken  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we were to execute this query now, we should see both tokens in our response. Nice!&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;signInWithPassword&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;refreshToken&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;eyJhb...StGg&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;eyJhb...FwKM&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;#x22;data&amp;#x22;: {    &amp;#x22;signInWithPassword&amp;#x22;: {      &amp;#x22;refreshToken&amp;#x22;: &amp;#x22;eyJhb...StGg&amp;#x22;,      &amp;#x22;token&amp;#x22;: &amp;#x22;eyJhb...FwKM&amp;#x22;    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is excellent! If we were to take a look at our tokens in &lt;code&gt;iex&lt;/code&gt;, we’ll see two tokens for the same user, with different purposes and expiration times. Nice!&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;peek&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;myRefreshToken&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, %{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;exp&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;1744927645&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;purpose&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;sub&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;user?id=myId&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;peek&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;myAccessToken&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, %{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;exp&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;1742339245&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;purpose&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;sub&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;user?id=myId&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;iex(1)&gt; AshAuthentication.Jwt.peek(&amp;#x22;myRefreshToken&amp;#x22;){:ok, %{  ...,  &amp;#x22;exp&amp;#x22; =&gt; 1744927645,  &amp;#x22;purpose&amp;#x22; =&gt; &amp;#x22;refresh_token&amp;#x22;,  &amp;#x22;sub&amp;#x22; =&gt; &amp;#x22;user?id=myId&amp;#x22;}}iex(2)&gt; AshAuthentication.Jwt.peek(&amp;#x22;myAccessToken&amp;#x22;){:ok, %{  ...,  &amp;#x22;exp&amp;#x22; =&gt; 1742339245,  &amp;#x22;purpose&amp;#x22; =&gt; &amp;#x22;user&amp;#x22;,  &amp;#x22;sub&amp;#x22; =&gt; &amp;#x22;user?id=myId&amp;#x22;}}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We can see now that we’re getting two tokens back, one a long-lived refresh token of 30 days and the other a short-lived access token of 1 hour. All that’s left now is to support exchanging the refresh token for a new access token. To do that, we’ll want to add a new action to our user resource:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;actions &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# existing actions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;read &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:exchange_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;get? &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;argument &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;sensitive?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;metadata &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;metadata &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;allow_nil?:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;prepare &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;set_context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(%{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;strategy_name:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:password&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;prepare &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Accounts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Preparations&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;ExchangeRefreshToken&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;actions do  # existing actions  read :exchange_refresh_token do    get? true    argument :refresh_token, :string, allow_nil?: false, sensitive?: true    metadata :token, :string, allow_nil?: false    metadata :refresh_token, :string, allow_nil?: false    prepare set_context(%{strategy_name: :password})    prepare MyApp.Accounts.User.Preparations.ExchangeRefreshToken  endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Here we’re defining a new action, which takes a single &lt;code&gt;refresh_token&lt;/code&gt; argument, and we’re returning two additional pieces of metadata - a new access token, and a new refresh token. Since our initial refresh token was only valid for 30 days, it’s important that each time a new refresh token is requested to be exchanged, we need to not only return a new (short-lived) access token, but also an updated refresh token. This gives us a built-in sliding 30 day expiration, so as long as the user is active within those 30 days, they won’t need to reauthenticate with their username and password.&lt;/p&gt;
&lt;p&gt;Now, let’s define this preparation. This preparation will be a bit more involved as we can’t rely on anything Ash did for us already, so let’s dive in. With this preparation we’ll want to do three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Verify the token&lt;/li&gt;
&lt;li&gt;Revoke the old refresh token&lt;/li&gt;
&lt;li&gt;Generate new access and refresh tokens&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Accounts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Preparations&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;ExchangeRefreshToken&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Preparation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.{&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.{&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Info&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;TokenResource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;prepare&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, options, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Get details about this auth strategy. We&apos;ll need this for revoking the tokens&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, strategy} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Info&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;find_strategy&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, context, options)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;query&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;before_action&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;verify_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;1, strategy, context))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;after_action&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;revoke_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;1, &amp;#x26;2, strategy, context))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;after_action&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_new_tokens&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;1, &amp;#x26;2, strategy, context))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;verify_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, strategy, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;token &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;get_argument&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, claims, &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;verify&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(token, strategy.resource, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;to_opts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(context)),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;         &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;verify_token_purpose&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(claims),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, primary_keys} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;primary_keys_from_subject&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(claims, strategy.resource) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# What we&apos;ve done is verified the token, verified the token purpose and extracted&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# the id of the user. We&apos;ll now update Ash to query off of this&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;query&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(^primary_keys)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, reason} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;-&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add_error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, [&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;], reason)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Do pattern matching to ensure that _only_ refresh tokens are accepted&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;verify_token_purpose&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(%{&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;purpose&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}), &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;verify_token_purpose&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;The token purpose is not valid&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Our Jwt provides a claim such as `user?id=myId` so let&apos;s parse that to&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# get the primary key for the user owning this token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# This is taken from the existing AshAuthentication library&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;primary_keys_from_subject&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(%{&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;sub&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; sub}, resource) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;primary_key_fields &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;resource&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Info&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&amp;#x26;to_string&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MapSet&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;key_parts &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;sub&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;URI&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;URI&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;decode_query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;provided_key_fields &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;key_parts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MapSet&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;MapSet&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;equal?&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(primary_key_fields, provided_key_fields) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;to_list&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(key_parts)}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;The token subject does not contain the required primary key fields&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;primary_keys_from_subject&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;The token does not contain a subject&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;defmodule MyApp.Accounts.User.Preparations.ExchangeRefreshToken do  use Ash.Resource.Preparation  require Ash.Query  alias Ash.{Query, Resource}  alias AshAuthentication.{Info, Jwt, TokenResource}  def prepare(query, options, context) do    # Get details about this auth strategy. We&amp;#x27;ll need this for revoking the tokens    {:ok, strategy} = Info.find_strategy(query, context, options)    query    |&gt; Query.before_action(&amp;#x26;verify_token(&amp;#x26;1, strategy, context))    |&gt; Query.after_action(&amp;#x26;revoke_refresh_token(&amp;#x26;1, &amp;#x26;2, strategy, context))    |&gt; Query.after_action(&amp;#x26;generate_new_tokens(&amp;#x26;1, &amp;#x26;2, strategy, context))  end  defp verify_token(query, strategy, context) do    token = Query.get_argument(query, :refresh_token)    with {:ok, claims, _} &lt;- Jwt.verify(token, strategy.resource, Ash.Context.to_opts(context)),         :ok &lt;- verify_token_purpose(claims),         {:ok, primary_keys} &lt;- primary_keys_from_subject(claims, strategy.resource) do      # What we&amp;#x27;ve done is verified the token, verified the token purpose and extracted      # the id of the user. We&amp;#x27;ll now update Ash to query off of this      query      |&gt; Query.filter(^primary_keys)    else      {:error, reason} -&gt;        Query.add_error(query, [:refresh_token], reason)    end  end  # Do pattern matching to ensure that _only_ refresh tokens are accepted  defp verify_token_purpose(%{&amp;#x22;purpose&amp;#x22; =&gt; &amp;#x22;refresh_token&amp;#x22;}), do: :ok  defp verify_token_purpose(_), do: {:error, &amp;#x22;The token purpose is not valid&amp;#x22;}  # Our Jwt provides a claim such as &amp;#x60;user?id=myId&amp;#x60; so let&amp;#x27;s parse that to  # get the primary key for the user owning this token  # This is taken from the existing AshAuthentication library  defp primary_keys_from_subject(%{&amp;#x22;sub&amp;#x22; =&gt; sub}, resource) do    primary_key_fields =      resource      |&gt; Resource.Info.primary_key()      |&gt; Enum.map(&amp;#x26;to_string/1)      |&gt; MapSet.new()    key_parts =      sub      |&gt; URI.parse()      |&gt; Map.get(:query, &amp;#x22;&amp;#x22;)      |&gt; URI.decode_query()    provided_key_fields =      key_parts      |&gt; Map.keys()      |&gt; MapSet.new()    if MapSet.equal?(primary_key_fields, provided_key_fields) do      {:ok, Enum.to_list(key_parts)}    else      {:error, &amp;#x22;The token subject does not contain the required primary key fields&amp;#x22; }    end  end  defp primary_keys_from_subject(_, _), do: {:error, &amp;#x22;The token does not contain a subject&amp;#x22;}end&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is looking good. So far, our preparation is taking the refresh_token argument, verifying it and extracting the subject details out of it, and telling Ash.Query to only query for users with that primary key.&lt;/p&gt;
&lt;p&gt;Next, we’ll want to implement our two &lt;code&gt;after_action&lt;/code&gt; hooks to actually revoke the tokens and generate new ones. Let’s implement those now.&lt;/p&gt;
&lt;p&gt;The first thing we’ll want to do is revoke the refresh token. This is important because we want to make sure our long-lived refresh tokens aren’t easily compromised.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;revoke_refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, [user], strategy, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;token_resource &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Info&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;authentication_tokens_token_resource!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(strategy.resource)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;token &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;get_argument&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Here we asked Ash to tell us what the token resource was, so we can&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# easily call the action to revoke it.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;case&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;TokenResource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;revoke&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(token_resource, token, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;to_opts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(context)) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;-&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, [user]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, reason} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;-&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, reason}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;defp revoke_refresh_token(query, [user], strategy, context) do  token_resource = Info.authentication_tokens_token_resource!(strategy.resource)  token = Query.get_argument(query, :refresh_token)  # Here we asked Ash to tell us what the token resource was, so we can  # easily call the action to revoke it.  case TokenResource.revoke(token_resource, token, Ash.Context.to_opts(context)) do    :ok -&gt;      {:ok, [user]}    {:error, reason} -&gt;      {:error, reason}  endend&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now, we’ll want to actually generate the new tokens:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_new_tokens&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(query, [user], strategy, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, refresh_token, &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_claims&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, context),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, token, &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;_claims&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;token_for_user&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, %{&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;purpose&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;to_opts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(context)) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;user &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;put_metadata&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, refresh_token)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;put_metadata&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, token)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, [user]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, reason} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;-&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, reason }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# Same helper method as in our sign in to generate preparation. It might be worth extracting&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;# this out to a helper method in the future&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;defp&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;generate_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;opts &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Ash&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;to_opts&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(context) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Keyword&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:token_lifetime&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, {&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:days&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;token_for_user&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(user, %{&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;purpose&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;refresh_token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}, opts)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;defp generate_new_tokens(query, [user], strategy, context) do  with {:ok, refresh_token, _claims} &lt;- generate_token(user, context),       {:ok, token, _claims} &lt;- Jwt.token_for_user(user, %{&amp;#x22;purpose&amp;#x22; =&gt; :user}, Ash.Context.to_opts(context)) do    user =      user      |&gt; Resource.put_metadata(:refresh_token, refresh_token)      |&gt; Resource.put_metadata(:token, token)    {:ok, [user]}  else    {:error, reason} -&gt;      {:error, reason }  endend# Same helper method as in our sign in to generate preparation. It might be worth extracting# this out to a helper method in the futuredefp generate_token(user, context) do  opts = Ash.Context.to_opts(context) |&gt; Keyword.put(:token_lifetime, {30, :days})  Jwt.token_for_user(user, %{&amp;#x22;purpose&amp;#x22; =&gt; &amp;#x22;refresh_token&amp;#x22;}, opts)end&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Similar to our sign-in action, we are generating new tokens and placing them on the user metadata. This allows the action to expose them to our API. Once we expose this method in our GraphQL schema, we should be able to call it to generate a new access token and refresh token.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;graphql&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;RefreshToken&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;refreshToken&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;refreshToken&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;my-refresh-token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#AE4B07&quot;&gt;refreshToken&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;query RefreshToken() {  refreshToken(refreshToken: &amp;#x22;my-refresh-token&amp;#x22;) {    token    refreshToken  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we call this with a valid refresh token, we should get the following response:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;refreshToken&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;refreshToken&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;eyJh...Ms_M&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FE&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;eyJhb...wNe0&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{  &amp;#x22;data&amp;#x22;: {    &amp;#x22;refreshToken&amp;#x22;: {      &amp;#x22;refreshToken&amp;#x22;: &amp;#x22;eyJh...Ms_M&amp;#x22;,      &amp;#x22;token&amp;#x22;: &amp;#x22;eyJhb...wNe0&amp;#x22;    }  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we call that query again with our old refresh token, we’ll get an error, as the previous token was revoked.&lt;/p&gt;
&lt;h3 id=&quot;next-steps&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#next-steps&quot;&gt;Next Steps&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This provides us with some pretty basic support for access and refresh tokens, and thanks to existing Ash Authentication functionality, implements several security best practices for us:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prevents token replay attacks thanks to revoking refresh tokens&lt;/li&gt;
&lt;li&gt;Reduces attack window as any leaked access tokens are short lived, and refresh tokens are only valid once&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A nice enhancement to the above implementation might be to also return the expiration time of the access token. This would allow the client to not have to decode the JWT token themselves to determine the expiration time. That can be achieved by adding an additional &lt;code&gt;metadata&lt;/code&gt; field on both actions, and extracting the expiration time from the generated token claim and setting it on the user metadata.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, claims} &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;-&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Jwt&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;peek&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(token)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;expires_in &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(claims, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;exp&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;user &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; user &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Resource&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;put_metadata&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:expires_in&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, expires_in)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;  {:ok, claims} &lt;- Jwt.peek(token)  expires_in = Map.get(claims, &amp;#x22;exp&amp;#x22;)  user = user |&gt; Resource.put_metadata(:expires_in, expires_in)&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;An even better next step, which we might explore in a future post, is to extract some of this out to an add-on, so some of the configuration (such as refresh token expiration) can be more easily configured. In addition, I didn’t provide robust error-handling for all of the edge cases in order to keep the post short, and it’d also be a best practice to return more specific AshAuthentication errors throughout. For example:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AshAuthentication&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;Errors&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--1:#6F42C1&quot;&gt;AuthenticationFailed&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;exception&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;strategy:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; strategy,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; query,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;caused_by:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; %{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;module:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;strategy:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; strategy,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;action:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:verify_token&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;message:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;Unable to verify token&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;{:error, AshAuthentication.Errors.AuthenticationFailed.exception(  strategy: strategy,  query: query,  caused_by: %{    module: __MODULE__,    strategy: strategy,    action: :verify_token,    message: &amp;#x22;Unable to verify token&amp;#x22;  })}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded></item><item><title>Stimulus and RxJS for an SPA Like Experience</title><link>https://www.mikewilson.dev/posts/stimulus-and-rxjs-for-a-spa-like-experience/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/stimulus-and-rxjs-for-a-spa-like-experience/</guid><description>See how we can use Stimulus and RxJS along with server rendered templates to create a single-page-app like experience by just adding some sprinkles of Javascript.</description><pubDate>Tue, 11 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I really enjoy using &lt;a href=&quot;https://stimulus.hotwired.dev/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Stimulus&lt;/a&gt; as a lightweight Javascript framework. With only a handful of attributes in your HTML and a few lines of Javascript, it’s possible to create some fairly advanced UI interactions. Lately, I’ve been combining Stimulus with &lt;a href=&quot;https://github.com/ReactiveX/rxjs&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;RxJS&lt;/a&gt; to provide some protections around those interactions and improve the user experience with minimal code. Combining Stimulus and a few operators from RxJS, it’s possible to provide a fluid, SPA-like experience for your users.&lt;/p&gt;
&lt;p&gt;I find this to be a great mix of clean code and improved usability, and was a technique I used quite a bit when building the new booking experience on &lt;a href=&quot;https://www.kwikcal.com&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Kwikcal.com&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-experience&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#the-experience&quot;&gt;The Experience&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For this post, we’ll pretend to create a single page where a user can select from different movies in a list and have the details of that movie loaded dynamically through an XHR request, and display the details in a bottom panel below. This is a fairly straight forward interaction but can have a few gotcha’s that are easily overlooked.&lt;/p&gt;
&lt;img alt=&quot;The basics of our app&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2048&quot; height=&quot;1200&quot; src=&quot;https://www.mikewilson.dev/_astro/app-skeleton-landing-page.DHNEL7sa_uyYW0.webp&quot; &gt;
&lt;p&gt;What we want is that when a user clicks on a thumbnail for a movie, we’ll dynamically load the details of that movie. We also want some loading indicators so the user knows that something is taking place, but only if the user is on a slow connection. Also, if the user is on a slow connection and clicks through on different movies, we want to make sure that we always display the movie the user last clicked on - regardless of which request finished last.&lt;/p&gt;
&lt;h3 id=&quot;basic-interaction&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#basic-interaction&quot;&gt;Basic Interaction&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To start with, we’ll want to setup Stimulus so that our user can click on a thumbnail and load the content. In our case, we’re using Rails as a backend to return a partial, but this should work with any backend framework that can render HTML.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-controller&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-cards&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movies&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; |movie| &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-card&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-target&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies.movieItem&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;           &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-action&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;click-&gt;movies#loadMovie&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;           &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-movie-url&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;movie_path&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;slug&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;])&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-item-content&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;image_tag&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movie[&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;thumb&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-item-detail text-center&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movie[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-detail&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Movie detail goes here...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div class=&amp;#x22;movies&amp;#x22; data-controller=&amp;#x22;movies&amp;#x22;&gt;  &lt;div class=&amp;#x22;movie-cards&amp;#x22;&gt;    &lt;% @movies.each do |movie| %&gt;      &lt;div class=&amp;#x22;movie-card&amp;#x22; data-target=&amp;#x22;movies.movieItem&amp;#x22;           data-action=&amp;#x22;click-&gt;movies#loadMovie&amp;#x22;           data-movie-url=&amp;#x22;&lt;%= movie_path(movie[:slug]) %&gt;&amp;#x22;&gt;        &lt;div class=&amp;#x22;movie-item-content&amp;#x22;&gt;          &lt;%= image_tag(movie[:thumb]) %&gt;          &lt;div class=&amp;#x22;movie-item-detail text-center&amp;#x22;&gt;            &lt;%= movie[:title] %&gt;          &lt;/div&gt;        &lt;/div&gt;      &lt;/div&gt;    &lt;% end %&gt;  &lt;/div&gt;  &lt;div class=&amp;#x22;movie-detail&amp;#x22;&gt;    Movie detail goes here...  &lt;/div&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Here is what our index page looks like, with some Stimulus attributes set up to define what our controller is, the targets for our movie item, and the URL for the partial that we will eventually load. This is what I love about Stimulus - all of our code set up is in HTML, and we’re able to even leverage things such as our route helpers so that our Javascript doesn’t even care about how our routes are structured. All of that logic lives in a single source - our backend app.&lt;/p&gt;
&lt;p&gt;Up above, we set up an action &lt;code&gt;data-action=&quot;click-&gt;movies#loadMovie&quot;&lt;/code&gt;. This tells Stimulus what action on our controller to call when a user clicks on that element. Let’s hook that up in our Controller.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;loadMovie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; target &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; event.currentTarget;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieSlug &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; target.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;getAttribute&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;data-movie-url&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;`User clicked on &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;${&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;movieSlug&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;export default class extends Controller {  loadMovie(event) {    let target = event.currentTarget;    let movieSlug = target.getAttribute(&amp;#x22;data-movie-url&amp;#x22;);    console.log(&amp;#x60;User clicked on ${movieSlug}&amp;#x60;);  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;So far we haven’t done much. We’ve added a method for our click event, and right now we’re just logging to the console what the user loaded. To get the data for the movie, we’ll pull in RxJS which has a lot of utility methods we’ll take advantage of.&lt;/p&gt;
&lt;h3 id=&quot;a-sprinkling-of-reactivity&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#a-sprinkling-of-reactivity&quot;&gt;A sprinkling of reactivity&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;RxJS is a library for doing reactive programming. If you’re coming from the Angular world, like I am, you’re probably familiar with it already. RxJS is a “library for composing asynchronous and event-based programs by using observable sequences”.&lt;/p&gt;
&lt;p&gt;It’s easier to see it in action, and the official &lt;a href=&quot;https://rxjs.dev/guide/overview&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;docs&lt;/a&gt; can explain the basics better than I could. Let’s add RxJS to our project and get going.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame is-terminal&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sr-only&quot;&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;yarn&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;rxjs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;yarn add rxjs&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Fortunately, RxJS as a library is broken down into smaller parts that we can use. The first thing we’ll want to do is set up some &lt;code&gt;Subject&lt;/code&gt;’s to observe for when a user clicks on a movie.&lt;/p&gt;
&lt;p&gt;First, we’ll want to import a few things from the &lt;code&gt;rxjs&lt;/code&gt; library. Specifically, &lt;code&gt;ajax&lt;/code&gt;, &lt;code&gt;Subject&lt;/code&gt; and &lt;code&gt;map&lt;/code&gt; as an operator.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Subject } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { map } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs/operators&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;import { Subject } from &amp;#x22;rxjs&amp;#x22;;import { map } from &amp;#x22;rxjs/operators&amp;#x22;;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Next, we want to set up a new Subject which we’ll observe. We’ll use the &lt;code&gt;connect&lt;/code&gt; lifecycle callback from Stimulus to set this up every time a new controller is created. This subscription will be used to listen to changes in the selected movie, and then output the URL to load. We’ll expand on this later on, but let’s take baby steps to get our observables in order.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Subject } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { map } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs/operators&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;movieItem$&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--0fw:bold;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;Subject&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupMovieClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;disconnect&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// We need to make sure that we unsubscribe to the stream,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// otherwise we could have memory leaks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;unsubscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;loadMovie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; target &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; event.currentTarget;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieSlug &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; target.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;getAttribute&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;data-movie-url&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movieSlug);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupMovieClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;movieUrl&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieUrl;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;`Displaying: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;${&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;import { Subject } from &amp;#x22;rxjs&amp;#x22;;import { map } from &amp;#x22;rxjs/operators&amp;#x22;;export default class extends Controller {  movieItem$ = new Subject();  connect() {    this.setupMovieClick();  }  disconnect() {    // We need to make sure that we unsubscribe to the stream,    // otherwise we could have memory leaks    this.movieItem$.unsubscribe();  }  loadMovie(event) {    let target = event.currentTarget;    let movieSlug = target.getAttribute(&amp;#x22;data-movie-url&amp;#x22;);    this.movieItem$.next(movieSlug);  }  setupMovieClick() {    this.movieItem$      .pipe(        map((movieUrl) =&gt; {          return movieUrl;        }),      )      .subscribe((response) =&gt; {        console.log(&amp;#x60;Displaying: ${response}&amp;#x60;);      });  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Every time you subscribe to an Observable, it’s important to remember that you must always unsubscribe, in order to prevent a memory leak.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’re new to RxJS this probably looks a bit crazy - so let’s go through it one section at a time.&lt;/p&gt;
&lt;p&gt;The first thing we want to do is set up an observable that we’ll listen to and &lt;em&gt;react&lt;/em&gt; to when a user event takes place. This is what our &lt;code&gt;Subject&lt;/code&gt; is - the &lt;code&gt;movieItem$&lt;/code&gt;. It’s common practice in RxJS to name our streams ending in a dollar sign, to further clarify what they are.&lt;/p&gt;
&lt;p&gt;Second, we create a private method to set up the movie click. Here, we use &lt;code&gt;pipe&lt;/code&gt; to set up a series of operations that take place every time a new item is added to the stream. In this initial example, we just want to map and return the URL. Later on, we’ll expand that logic a bit.&lt;/p&gt;
&lt;p&gt;Finally, we call &lt;code&gt;.subscribe&lt;/code&gt; on that stream and specify a callback to take place after all of the pipe operations have completed. It’s important to know that nothing takes place until we actually subscribe to a stream. So if you had native events or XHR requests, they wouldn’t actually fire until we subscribe to the stream.&lt;/p&gt;
&lt;p&gt;Finally, we take our old &lt;code&gt;loadMovie&lt;/code&gt; method and change it up a bit. We tell the stream that there is a new value, by calling &lt;code&gt;.next&lt;/code&gt; with the path to the partial. This is what triggers our subscription callback to fire, which calls our console.log statement.&lt;/p&gt;
&lt;p&gt;Now that this is done, if we refresh the page and click on a thumbnail we should see the URL of the movie to load in our console.&lt;/p&gt;
&lt;h3 id=&quot;loading-the-movie-details&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#loading-the-movie-details&quot;&gt;Loading the movie details&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we know our button clicks are all wired up and reactive, let’s go and retrieve the data for the movie details.&lt;/p&gt;
&lt;p&gt;The first thing we’ll want to do is import the &lt;code&gt;ajax&lt;/code&gt; command from RxJS. We do that by adding the following to the top of our file:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { ajax } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs/ajax&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { ajax } from &amp;#x22;rxjs/ajax&amp;#x22;;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We’ll also want to import &lt;code&gt;switchMap&lt;/code&gt; from &lt;code&gt;rxjs/operators&lt;/code&gt;, as we’ll want to use this for the XHR requests instead of a simple map operation.&lt;/p&gt;
&lt;p&gt;Here is what our pipe should now look like:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupMovieClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;switchMap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;movieUrl&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;ajax&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;method&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieUrl,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;responseType&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; response.response;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(response);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;setupMovieClick() {  this.movieItem$    .pipe(      switchMap((movieUrl) =&gt; {        return ajax({          method: &amp;#x27;GET&amp;#x27;,          url: movieUrl,          responseType: &amp;#x27;text&amp;#x27;        });      }),      map((response) =&gt; {        return response.response;      })    )    .subscribe((response) =&gt; {      console.log(response);    });}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What we’re doing is instead of doing a &lt;code&gt;map&lt;/code&gt; to output the URL, we do a &lt;code&gt;switchMap&lt;/code&gt; and return our &lt;code&gt;ajax&lt;/code&gt; call to load our partial.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;switchMap&lt;/code&gt; is similar to a regular &lt;code&gt;map&lt;/code&gt; but has the added benefit of only returning the latest request. This means that if the user is on a less than ideal connection and is quickly clicking on many movie thumbnails, &lt;code&gt;switchMap&lt;/code&gt; will do all the dirty work of making sure that only the last click will be processed, instead of the last &lt;em&gt;request&lt;/em&gt; result.&lt;/p&gt;
&lt;p&gt;What this means in practice is that our clicks don’t get out of sync. Say, for example, the user clicks on movie 1, and that takes 450ms to respond, but before that happens, the user clicks on movie 2, and now that only takes 100ms to respond. Even though the last &lt;em&gt;response&lt;/em&gt; is for movie 1, the last requested item is movie 2, and that is what the map will return.&lt;/p&gt;
&lt;p&gt;In fact, &lt;code&gt;switchMap&lt;/code&gt; is even smart enough to cancel any of the old HTTP requests for us!&lt;/p&gt;
&lt;img alt=&quot;Screenshot of the Chrome devtools showing our canceled requests&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1183&quot; height=&quot;808&quot; src=&quot;https://www.mikewilson.dev/_astro/canceled-xhr-requests.g_66BZO1_1IW6LU.webp&quot; &gt;
&lt;p&gt;Now what we want to do after our &lt;code&gt;switchMap&lt;/code&gt;, which returns the results of our XHR request, is return a &lt;code&gt;map&lt;/code&gt; which takes the text response from our request and maps that to any subscriber.&lt;/p&gt;
&lt;p&gt;If we run this now, we’ll get the movie details for the last item the user clicked on, regardless of the completion order of the network requests.&lt;/p&gt;
&lt;h3 id=&quot;displaying-the-details&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#displaying-the-details&quot;&gt;Displaying the details&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we have the movie details as a string from our partial, we can use some Stimulus targets to inject that content into the DOM. We’ll want to take our old placeholder div and tell Stimulus about it, by specifying it as a target.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-detail&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-target&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies.movieDetails&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Movie detail goes here...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div class=&amp;#x22;movie-detail&amp;#x22; data-target=&amp;#x22;movies.movieDetails&amp;#x22;&gt;  Movie detail goes here...&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Then, at the top of our controller, we tell Stimulus about it, so we can access it from our controller.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;static targets &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movieDetails&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;static targets = [&amp;#x27;movieDetails&amp;#x27;];&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Finally, we’ll modify our subscription callback to update the innerHTML of this element:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieDetailsTarget.innerHTML &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; response;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;.subscribe((response) =&gt; {  this.movieDetailsTarget.innerHTML = response;});&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With this, we should now see the movie details as we click around on the screen. Nice!&lt;/p&gt;
&lt;img alt=&quot;Clicking to load details&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;480&quot; height=&quot;277&quot; src=&quot;https://www.mikewilson.dev/_astro/clicking-to-load-details-opt.KPFDyXOj_Z1L3CCv.webp&quot; &gt;
&lt;h3 id=&quot;loading-indicator&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#loading-indicator&quot;&gt;Loading Indicator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far, this is looking pretty good. The user can click on a movie card and see the details of the movie. Even better, with our reactive programming approach, even users on a less than ideal connection don’t get any state out of sync by having XHR requests returned out of order. They always see the last item they clicked on. One challenge left is to add a loading indicator, so users on a slower connection can see a visual indicator that we’re fetching their data.&lt;/p&gt;
&lt;p&gt;To keep things simple, we’ll use an animated gif from &lt;a href=&quot;https://loader.io&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;loader.io&lt;/a&gt;, and put it in our template. We could also just have easily used an SVG and some CSS, but that’s out of scope for this article.&lt;/p&gt;
&lt;p&gt;The first thing we’ll want to do is to put the loading state in our template and tell our Stimulus controller about it.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading-indicator hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;     &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-target&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies.movieDetailsLoading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;image_tag&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading.gif&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div class=&amp;#x22;loading-indicator hidden&amp;#x22;     data-target=&amp;#x22;movies.movieDetailsLoading&amp;#x22;&gt;  &lt;%= image_tag(&amp;#x22;loading.gif&amp;#x22;) %&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;static targets &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movieDetails&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movieDetailsLoading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;static targets = [&amp;#x27;movieDetails&amp;#x27;, &amp;#x27;movieDetailsLoading&amp;#x27;];&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now that Stimulus knows about it, we can do some simple CSS to hide and show the movie details content and the loading content. Our code is getting a bit complex, so we should probably take the opportunity to refactor the displaying of the loading state and displaying of the movie content into separate methods.&lt;/p&gt;
&lt;p&gt;So how do we hook into our subscription to know when a movie is loading? Fortunately, RxJS has our back, and we can simply &lt;code&gt;tap&lt;/code&gt; into the pipe to call our loading code without affecting anything that gets returned from the pipe. Let’s see what that looks like, along with our refactored code.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupMovieClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;switchMap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;movieUrl&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;ajax&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;method&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieUrl,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;responseType&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; response.response;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayMovieContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(response);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieDetailsLoadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieDetailsTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayMovieContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movieContent) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieDetailsTarget.innerHTML &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieContent;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieDetailsLoadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieDetailsTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;setupMovieClick() {  this.movieItem$    .pipe(      tap(() =&gt; {        this.displayLoadingState();      }),      switchMap((movieUrl) =&gt; {        return ajax({          method: &amp;#x27;GET&amp;#x27;,          url: movieUrl,          responseType: &amp;#x27;text&amp;#x27;        });      }),      map((response) =&gt; {        return response.response;      })    )    .subscribe((response) =&gt; {      this.displayMovieContent(response);    });}displayLoadingState() {  this.movieDetailsLoadingTarget.classList.remove(&amp;#x27;hidden&amp;#x27;);  this.movieDetailsTarget.classList.add(&amp;#x27;hidden&amp;#x27;);}displayMovieContent(movieContent) {  this.movieDetailsTarget.innerHTML = movieContent;  this.movieDetailsLoadingTarget.classList.add(&amp;#x27;hidden&amp;#x27;);  this.movieDetailsTarget.classList.remove(&amp;#x27;hidden&amp;#x27;);}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Here what we do is we call &lt;code&gt;tap&lt;/code&gt; with a function that gets called in our event stream, after an item is clicked, and before the ajax request is fired off. Then, we target the elements through Stimulus and add some utility classes to hide the different elements.&lt;/p&gt;
&lt;p&gt;Now, when we click on a movie, we’ll see a little spinner display before the movie details are displayed. Great!&lt;/p&gt;
&lt;p&gt;The only downside here is that if a user is on a fast connection, then they’ll see a quick flash of the loading spinner before that gets displayed.&lt;/p&gt;
&lt;h3 id=&quot;loading-indicator-timeout&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#loading-indicator-timeout&quot;&gt;Loading Indicator Timeout&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One way to work around the loading indicator for users on a fast connection is to put the code to display the spinner on a timeout. If it’s been 250 milliseconds and we haven’t gotten data from the server back, we should display a spinner. As soon as we get data back, let’s cancel the timeout. This has the benefit of only displaying the spinner for users on a slow connection.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupMovieClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; loadingIndicatorTimeout;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;loadingIndicatorTimeout &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;switchMap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;movieUrl&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;ajax&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;method&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieUrl,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;responseType&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(loadingIndicatorTimeout);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; response.response;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayMovieContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(response);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;setupMovieClick() {  let loadingIndicatorTimeout;  this.movieItem$    .pipe(      tap(() =&gt; {        loadingIndicatorTimeout = setTimeout(() =&gt; {          this.displayLoadingState();        }, 250);      }),      switchMap((movieUrl) =&gt; {        return ajax({          method: &amp;#x27;GET&amp;#x27;,          url: movieUrl,          responseType: &amp;#x27;text&amp;#x27;        });      }),      tap(() =&gt; {        clearTimeout(loadingIndicatorTimeout);      }),      map((response) =&gt; {        return response.response;      })    )    .subscribe((response) =&gt; {      this.displayMovieContent(response);    });}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What we’re doing is taking our initial &lt;code&gt;tap&lt;/code&gt; function, and wrapping it in a &lt;code&gt;setTimeout&lt;/code&gt; to run after 250ms. Then, in a new &lt;code&gt;tap&lt;/code&gt; operation, we cancel the timeout, as that means we’ve retrieved the data from the server. If the timeout never reached 250, such as a user on a fast connection, then the loading spinner never fires. If it’s already been displayed, then nothing happens, as the callback already took place.&lt;/p&gt;
&lt;img alt=&quot;Click on details on a slow connection and user will see the loading indicator&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;480&quot; height=&quot;266&quot; src=&quot;https://www.mikewilson.dev/_astro/click-to-load-with-spinner.CviyYL9Z_Z1fAbyf.webp&quot; &gt;
&lt;h3 id=&quot;final-touches&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#final-touches&quot;&gt;Final Touches&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Wow - we’ve achieved a lot, with only a handful of code in our Stimulus controller. There’s one final touch that we should add. Currently, if a user clicks on a movie, we display that movie in the details panel below. However, if a user continually clicks on the same movie tile, we keep making the same request over and over again.&lt;/p&gt;
&lt;p&gt;Once again, RxJS has our back - this time with the &lt;code&gt;distinctUntilChanged&lt;/code&gt; operator. Add this operator as the first operator in our pipeline, and the rest will only fire when the stream item has changed - which in our case is the movie detail URL to load.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;distinctUntilChanged&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;loadingIndicatorTimeout &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// etc etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;this.movieItem$  .pipe(    distinctUntilChanged(),    tap(() =&gt; {      loadingIndicatorTimeout = setTimeout(() =&gt; {        this.displayLoadingState();      }, 250);    }),    // etc etc.&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Another behavior we should add is to highlight the selected movie. This is something we can do using Stimulus in our click handler.&lt;/p&gt;
&lt;p&gt;Since we’ve already added a data-target for our movie items in our template, we just need to add those as a static target in our controller. From there, we can use basic CSS and DOM manipulation to identify the selected card.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;static targets &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movieItem&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movieDetails&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movieDetailsLoading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;loadMovie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(event) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; target &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; event.currentTarget;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movieSlug &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; target.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;getAttribute&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;data-movie-url&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItemTargets.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;movieItem&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;movieItem.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;selected&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;target.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;selected&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.movieItem$.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movieSlug);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;static targets = [&amp;#x27;movieItem&amp;#x27;, &amp;#x27;movieDetails&amp;#x27;, &amp;#x27;movieDetailsLoading&amp;#x27;];loadMovie(event) {  let target = event.currentTarget;  let movieSlug = target.getAttribute(&amp;#x27;data-movie-url&amp;#x27;);  this.movieItemTargets.forEach((movieItem) =&gt; {    movieItem.classList.remove(&amp;#x27;selected&amp;#x27;);  });  target.classList.add(&amp;#x27;selected&amp;#x27;);  this.movieItem$.next(movieSlug);}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now our final product is looking pretty good. As the user clicks around the movies, they get visual indicators if they are loading, visual indicators of their selected movie and any slow or untimely requests are easily handled for us by RxJS.&lt;/p&gt;
&lt;p&gt;Not bad for around 75 lines of Javascript!&lt;/p&gt;
&lt;img alt=&quot;Final product&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;640&quot; height=&quot;354&quot; src=&quot;https://www.mikewilson.dev/_astro/final-movie-click-throughs.DSEELSat_ZYU5NF.webp&quot; &gt;
&lt;p&gt;If you’d like to see the final code, please feel free to browse the &lt;a href=&quot;https://github.com/mike1o1/stimulus-rxjs-example&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;mike1o1/stimulus-rxjs-example&lt;/a&gt; repository.&lt;/p&gt;</content:encoded></item><item><title>Stimulus and RxJS For Improved Loading States</title><link>https://www.mikewilson.dev/posts/stimulus-and-rxjs-loading-state/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/stimulus-and-rxjs-loading-state/</guid><description>See how we can use Stimulus and RxJS primitives to improve the handling of loading states in response to user actions.</description><pubDate>Fri, 15 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve written before about using &lt;a href=&quot;https://www.mikewilson.dev/posts/stimulus-and-rxjs-for-a-spa-like-experience/&quot;&gt;Stimulus and RxJS&lt;/a&gt;, but I wanted to write again about an improved way of handling loading states using RxJS and Stimulus.&lt;/p&gt;
&lt;p&gt;Displaying a loading state is a great way to indicate to the user that there is something happening in response to a button click, but sometimes if the user is on a fast connection or machine, displaying that loading state too quickly can have a negative impact.&lt;/p&gt;
&lt;p&gt;For example, if a user action takes ~100ms to process something, displaying and then quickly hiding a loading state will just show a flash of content and could leave the user a little bit confused.&lt;/p&gt;
&lt;p&gt;Fortunately, RxJS makes handling this extremely simple to implement. In the previous post we implemented this using &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;clearTimeout&lt;/code&gt;, but now we’ll see how this can be improved by using RxJS primitives.&lt;/p&gt;
&lt;h3 id=&quot;loading-state&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#loading-state&quot;&gt;Loading State&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A loading state is something we want to display when the user clicks an action and the application needs to retrieve data from a remote source. Since we don’t know how long that data retrieval will take place, we display a loading state letting the user know something is happening.&lt;/p&gt;
&lt;p&gt;Here is an example of how this could look in response to a button click&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupClickEvent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.newClick&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Fake delay&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(content) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingState.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// display actual content&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;setupClickEvent() {  this.newClick    .pipe(      tap(() =&gt; {        this.displayLoadingState();      }),      delay(500)  // Fake delay    )    .subscribe(() =&gt; {      this.displayContent();    });}displayLoadingState() {  this.loadingTarget.classList.remove(&amp;#x27;hidden&amp;#x27;);}displayContent(content) {  this.loadingState.classList.add(&amp;#x27;hidden&amp;#x27;);  // display actual content}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you’ve used RxJS before (or read the previous post), this should be pretty familiar. When a new click is observed, we immediately display the loading state. Once the data is retrieved from the remote source, we hide the loading state.&lt;/p&gt;
&lt;h3 id=&quot;adding-a-loading-delay&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#adding-a-loading-delay&quot;&gt;Adding a Loading Delay&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To improve the experience, so we don’t get a flash of a loading indicator for users on a fast connection, we should add a delay between the time that the loading takes place and when we actually modify the DOM to do display it. Basically, we separate the loading state from the logic of actually displaying the loading state.&lt;/p&gt;
&lt;p&gt;To do this, we first want to add a new &lt;code&gt;Subject&lt;/code&gt; for handling the loading state.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Controller } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;stimulus&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { Subject } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; { tap, delay, filter, debounceTime } &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;rxjs/operators&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;targets&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;newClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--0fw:bold;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;Subject&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;isLoading&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--0fw:bold;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;Subject&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;loadingDelay&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;350&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupClickEvent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;myClick&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.newClick.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupClickEvent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.newClick&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;tap&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.delayTarget.innerText &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.isLoading.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;150&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.isLoading.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.delayTarget.innerText &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;`Data available`&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Display content&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;setupLoadingState&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Subscribe to when loading state is enabled&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// but wait 250ms before updating the DOM and displaying it&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.isLoading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;debounceTime&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingDelay),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; value &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Subscribe to when loading state is disabled&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// and hide it immediately&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.isLoading.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;pipe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;value)).&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;subscribe&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { Controller } from &amp;#x22;stimulus&amp;#x22;;import { Subject } from &amp;#x22;rxjs&amp;#x22;;import { tap, delay, filter, debounceTime } from &amp;#x22;rxjs/operators&amp;#x22;;export default class extends Controller {  static targets = [&amp;#x22;loading&amp;#x22;, &amp;#x22;delay&amp;#x22;];  newClick = new Subject();  isLoading = new Subject();  loadingDelay = 350;  connect() {    this.setupClickEvent();    this.setupLoadingState();  }  myClick(event) {    this.newClick.next();  }  setupClickEvent() {    this.newClick      .pipe(        tap(() =&gt; {          this.delayTarget.innerText = &amp;#x22;&amp;#x22;;          this.isLoading.next(true);        }),        delay(150),      )      .subscribe(() =&gt; {        this.isLoading.next(false);        this.delayTarget.innerText = &amp;#x60;Data available&amp;#x60;;        // Display content      });  }  setupLoadingState() {    // Subscribe to when loading state is enabled    // but wait 250ms before updating the DOM and displaying it    this.isLoading      .pipe(        debounceTime(this.loadingDelay),        filter((value) =&gt; value === true),      )      .subscribe(() =&gt; {        this.loadingTarget.classList.remove(&amp;#x22;hidden&amp;#x22;);      });    // Subscribe to when loading state is disabled    // and hide it immediately    this.isLoading.pipe(filter((value) =&gt; !value)).subscribe(() =&gt; {      this.loadingTarget.classList.add(&amp;#x22;hidden&amp;#x22;);    });  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The bulk of the work takes place in the &lt;code&gt;handleLoadingState&lt;/code&gt; method and is done completely with RxJS primitives &lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;debounceTime&lt;/code&gt;. What we do is create two subscriptions - the first for when loading state is enabled, and the second for when the loading state is turned off.&lt;/p&gt;
&lt;p&gt;The difference between the two is that we add a debounce between the time the loading state is turned on and the time that we actually display it. If the loading state is disabled between that time, it will fail the filter check and the loading state stays hidden.&lt;/p&gt;
&lt;p&gt;We also updated our button click code to simply call &lt;code&gt;next&lt;/code&gt; on the &lt;code&gt;isLoading&lt;/code&gt; subject with the appropriate state (true or false). This further decouples the displaying of the loading state from the retrieving of the data. All the retrieving of the data needs to do is tell RxJS that something is loading or is done loading. Everything else happens by subscribing to that &lt;code&gt;Subject&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we change the fake delay on our button click action to be higher than 350, we’ll see the loading indicator display. If we change the fake delay to less than 350, we won’t show the loading indicator at all.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With just a few more lines of code, we’ve improved our loading state logic quite a bit. We’ve decoupled the loading state itself from the logic that handles the display of the loading state.&lt;/p&gt;
&lt;p&gt;If you’d like to see the final code, please feel free to browse the &lt;a href=&quot;https://github.com/mike1o1/stimulus-rxjs-loading-state&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;mike1o1/stimulus-rxjs-loading-state&lt;/a&gt; repository.&lt;/p&gt;</content:encoded></item><item><title>Using Hotwire with Rails for a SPA like experience</title><link>https://www.mikewilson.dev/posts/using-hotwire-with-rails-for-a-spa-like-experience/</link><guid isPermaLink="true">https://www.mikewilson.dev/posts/using-hotwire-with-rails-for-a-spa-like-experience/</guid><description>Previously, we created a SPA-like experience using Stimulus and RxJs. Let&apos;s recreate that using Turbo Frames, and add use Stimulus to add some polish, such as loading states and keeping track of the selected item.</description><pubDate>Thu, 05 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In a &lt;a href=&quot;https://www.mikewilson.dev/posts/stimulus-and-rxjs-for-a-spa-like-experience/&quot;&gt;previous post&lt;/a&gt; I wrote about using
Rails and Stimulus to create a SPA like experience with page navigation. Since then, the Basecamp team has
released &lt;a href=&quot;https://www.hotwired.dev&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Hotwire&lt;/a&gt; to allow for similar functionality but with little-to-no Javascript.&lt;/p&gt;
&lt;p&gt;We’ll take a look at updating the experience from the previous post to
use &lt;a href=&quot;https://turbo.hotwired.dev/reference/frames&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Turbo Frames&lt;/a&gt; to provide inter-page navigation. Later, we’ll sprinkle
in some Stimulus to provide some visual feedback to the user with loading states.&lt;/p&gt;
&lt;p&gt;To recap, here is what we’ll be building.&lt;/p&gt;
&lt;img alt=&quot;Final product&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;640&quot; height=&quot;354&quot; src=&quot;https://www.mikewilson.dev/_astro/final-movie-click-throughs.DSEELSat_ZYU5NF.webp&quot; &gt;
&lt;h3 id=&quot;first-steps&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#first-steps&quot;&gt;First Steps&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With Hotwire/Turbo, we have a concept of a Turbo Frame. A Turbo Frame is similar to frames of old, but can be thought of
as a tool to break-down a page into separate areas of content. In our case, we’ll have two frames - one for the list of
movies, and another for where we display the details about the selected movie.&lt;/p&gt;
&lt;p&gt;Thinking in frames takes some getting used to, but it can be powerful once it clicks. The documentation has some good
examples, but it still took a bit for it to click with me. In short, if you’re navigating &lt;em&gt;within&lt;/em&gt; a frame, then if the
response coming back also has that same frame id within it, Turbo will replace it for you automatically. If we don’t
want that, we can set the turbo frame target, either to “_top”, or to a specific turbo frame elsewhere on the page.&lt;/p&gt;
&lt;p&gt;Let’s see what our page looks like if we break it down into frames - one for the index, another for the content.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;https://www.mikewilson.dev/_astro/ec.a8zzi.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;https://www.mikewilson.dev/_astro/ec.0vx5m.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; turbo_frame_tag &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:index&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;class:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-cards&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movies&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; |movie| &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; link_to &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;movie_path&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movie[&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;slug&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-card&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;&amp;#x3C;%= image_tag(movie[:thumb]) %&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;div &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-item-detail text-center&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movie[&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;&amp;#x3C;/div&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;&amp;#x3C;% end %&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;&amp;#x3C;%= turbo_frame_tag :details, class: &quot;movie-detail&quot; do %&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; a movie &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; more details...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;&amp;#x3C;/div&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div class=&amp;#x22;movies&amp;#x22;&gt;  &lt;%= turbo_frame_tag :index, class: &amp;#x22;movie-cards&amp;#x22; do %&gt;    &lt;% @movies.each do |movie| %&gt;      &lt;%= link_to movie_path(movie[:slug], class: &amp;#x22;movie-card&amp;#x22; do %&gt;        &lt;%= image_tag(movie[:thumb]) %&gt;        &lt;div class=&amp;#x22;movie-item-detail text-center&amp;#x22;&gt;          &lt;%= movie[:title] %&gt;        &lt;/div&gt;      &lt;% end %&gt;    &lt;% end %&gt;  &lt;% end %&gt;  &lt;%= turbo_frame_tag :details, class: &amp;#x22;movie-detail&amp;#x22; do %&gt;    Select a movie for more details...  &lt;% end %&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;As a refresher, let’s see what our old “show” view looked like. Pretty barebones, just some details about the movie.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Title: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Summary: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:summary&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Director: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:director&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Length: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:length&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Genre: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:genre&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Year: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:year&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;p&gt;  Title: &lt;%= @movie[:title] %&gt;&lt;/p&gt;&lt;p&gt;  Summary: &lt;%= @movie[:summary] %&gt;&lt;/p&gt;&lt;p&gt;  Director: &lt;%= @movie[:director] %&gt;&lt;/p&gt;&lt;p&gt;  Length: &lt;%= @movie[:length] %&gt;&lt;/p&gt;&lt;p&gt;  Genre: &lt;%= @movie[:genre] %&gt;&lt;/p&gt;&lt;p&gt;  Year: &lt;%= @movie[:year] %&gt;&lt;/p&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This setup will get us our frames. If we were to click on one of the movie cards, we’d see our index disappear and an
error in the console.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#f8f8f2;--1:#24292e&quot;&gt;Response has no matching &amp;#x3C;turbo-frame id=&quot;index&quot;&gt; element&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;Response has no matching &lt;turbo-frame id=&amp;#x22;index&amp;#x22;&gt; element&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;As mentioned above, since our link was within a turbo-frame element, Turbo is expecting the html response to contain a
matching &lt;code&gt;turbo-frame&lt;/code&gt; element, and Turbo will swap out the content for us. Cool, let’s just wrap the &lt;code&gt;show&lt;/code&gt; view in
a &lt;code&gt;turbo-frame&lt;/code&gt; element with an id of “index”, right? That won’t quite work because then we’d be replacing the index.&lt;/p&gt;
&lt;p&gt;Since we’re deviating from the default navigation behavior, we need to do two things to instruct Turbo how to handle our
navigation.&lt;/p&gt;
&lt;p&gt;First, we need to wrap our response in a turbo frame, but with an id of “details”. This should be straight forward,
let’s update our &lt;code&gt;show&lt;/code&gt; template and wrap the response in a turbo-frame.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; turbo_frame_tag &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Title: &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movie&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= turbo_frame_tag :details do %&gt;  &lt;p&gt;    Title: &lt;%= @movie[:title] %&gt;  &lt;/p&gt;  ...&lt;% end %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Second, we need to tell Turbo that when a user clicks on a link inside the &lt;code&gt;index&lt;/code&gt; frame, to target the &lt;code&gt;details&lt;/code&gt; frame.
Let’s update our index template and add a &lt;code&gt;data-turbo-frame&lt;/code&gt; attribute, and tell Turbo to update the &lt;code&gt;details&lt;/code&gt; frame
when navigation is completed.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; link_to &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;movie_path&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movie[&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;slug&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-card&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;data-turbo-frame&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;&amp;#x3C;%= image_tag(movie[:thumb]) %&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;div &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-item-detail text-center&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; movie[&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;title&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;&amp;#x3C;/div&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;%&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;%= link_to movie_path(movie[:slug], class: &amp;#x22;movie-card&amp;#x22;, &amp;#x22;data-turbo-frame&amp;#x22;: &amp;#x22;details&amp;#x22; do %&gt;  &lt;%= image_tag(movie[:thumb]) %&gt;  &lt;div class=&amp;#x22;movie-item-detail text-center&amp;#x22;&gt;    &lt;%= movie[:title] %&gt;  &lt;/div&gt;&lt;% end %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With these changes made, if we refresh the page and click on a movie card, we’ll see the details update. Very cool!
We’re about 80% of the way towards a SPA like experience, and we haven’t even written a single line of
Javascript/Typescript - not bad!&lt;/p&gt;
&lt;p&gt;If we pay close attention, we’ll see that there is some visual feedback that’s missing. There’s nothing to indicate to
the user that an action is taking place. If they’re on a slow connection, or the details takes awhile to load, they
won’t know that anything happened as there is no loading state. Second, the index frame never gets updated to show which
movie is selected.&lt;/p&gt;
&lt;p&gt;In the SPA example in the previous post, we were able to address both concerns through Javascript and using loading
states. Fortunately, we can still implement both of those with a simple &lt;a href=&quot;https://stimulus.hotwired.dev/&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Stimulus&lt;/a&gt;
controller.&lt;/p&gt;
&lt;h3 id=&quot;stimulus-for-visual-feedback&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#stimulus-for-visual-feedback&quot;&gt;Stimulus For Visual Feedback&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the old previous post, we used Stimulus to implement our own click handlers for when a user clicked on a movie, and
we used RxJs to handle fetching of the html content, and we updated the DOM ourselves. All of that is now taken care of
by Turbo, but we can still use Stimulus to handle some of the visual feedback. Let’s first create our Stimulus
controller and tackle the issue of loading states.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// We&apos;ll name this &quot;navigation_controller&quot;, for lack of a better term right now.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;targets&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoading&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.contentTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.contentTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;// We&amp;#x27;ll name this &amp;#x22;navigation_controller&amp;#x22;, for lack of a better term right now.export default class extends Controller {  static targets = [&amp;#x22;content&amp;#x22;, &amp;#x22;loading&amp;#x22;];  displayLoading(event) {    this.loadingTarget.classList.remove(&amp;#x22;hidden&amp;#x22;);    this.contentTarget.classList.add(&amp;#x22;hidden&amp;#x22;);  }  displayContent() {    this.loadingTarget.classList.add(&amp;#x22;hidden&amp;#x22;);    this.contentTarget.classList.remove(&amp;#x22;hidden&amp;#x22;);  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We’ve added two targets - the content of the item, and some loading content. When we want to display the loading state,
we hide the content target and show the loading target, and do the opposite when we want to display the content.&lt;/p&gt;
&lt;p&gt;How do we incorporate the controller into our template? Let’s first add our targets and use the controller.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-controller&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;navigation&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; turbo_frame_tag &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:index&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;class:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-cards&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;&amp;#x3C;!-- ... --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; turbo_frame_tag &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:details&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;class:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-detail&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;data-navigation-target&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;Select a movie for more details...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading-indicator hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;       &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-navigation-target&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;image_tag&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading.gif&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div class=&amp;#x22;movies&amp;#x22; data-controller=&amp;#x22;navigation&amp;#x22;&gt;  &lt;%= turbo_frame_tag :index, class: &amp;#x22;movie-cards&amp;#x22; do %&gt;    &lt;!-- ... --&gt;  &lt;% end %&gt;  &lt;%= turbo_frame_tag :details, class: &amp;#x22;movie-detail&amp;#x22;, &amp;#x22;data-navigation-target&amp;#x22;: &amp;#x22;content&amp;#x22; do %&gt;    Select a movie for more details...  &lt;% end %&gt;  &lt;div class=&amp;#x22;loading-indicator hidden&amp;#x22;       data-navigation-target=&amp;#x22;loading&amp;#x22;&gt;    &lt;%= image_tag(&amp;#x22;loading.gif&amp;#x22;) %&gt;  &lt;/div&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With this update we’ve done two things. First, we added our loading spinner to the DOM, in an initial state of hidden,
and we flagged our turbo-frame as the content target for our navigation controller.&lt;/p&gt;
&lt;p&gt;If we reload and click around, though, we’ll see that nothing has happened. That shouldn’t be too surprising, because we
haven’t told Stimulus how to actually invoke our two method for displaying the loading or content state.&lt;/p&gt;
&lt;p&gt;Taking a look at the &lt;a href=&quot;https://turbo.hotwired.dev/reference/events&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;Turbo documentation&lt;/a&gt;, there are a few events that
stand-out as something we might be able to hook into. The first is the &lt;code&gt;turbo:before-fetch-request&lt;/code&gt; event, which is
fired before Turbo issues a request to load the page. This sounds like it would be a good time to update the DOM to
display a loading state. Let’s wire that up.&lt;/p&gt;
&lt;p&gt;The documentation states that these events are fired on the document, and fortunately Stimulus provides an easy way to
hook into those.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-controller&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;navigation&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-action&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;turbo:before-fetch-request@document-&gt;navigation#displayLoading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;&amp;#x3C;!-- ... --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div  class=&amp;#x22;movies&amp;#x22;  data-controller=&amp;#x22;navigation&amp;#x22;  data-action=&amp;#x22;turbo:before-fetch-request@document-&gt;navigation#displayLoading&amp;#x22;&gt;  &lt;!-- ... --&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;What we’re doing now, is anytime Turbo begins a request, we trigger the &lt;code&gt;displayLoading&lt;/code&gt; action on our Stimulus
controller. If we were to reload the page now, we’d see the loading state display. However, we are not notified when the
navigation is complete, so we don’t really know when to then show the content.&lt;/p&gt;
&lt;p&gt;Turbo fires another event when the request is completed, the &lt;code&gt;turbo:before-fetch-response&lt;/code&gt;. This is confusing, because
it has the term “before” in it, but it basically means that the response for the content is available, which means the
http request completed, so we can hide our loading state, and unhide our content. Let’s wire that up the same way we did
our loading state.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-controller&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;navigation&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-action&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;turbo:before-fetch-request@document-&gt;navigation#displayLoading&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C;--1:#032F62&quot;&gt;turbo:before-fetch-response@document-&gt;navigation#displayContent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E9F284;--1:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;&amp;#x3C;!-- ... --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div  class=&amp;#x22;movies&amp;#x22;  data-controller=&amp;#x22;navigation&amp;#x22;  data-action=&amp;#x22;     turbo:before-fetch-request@document-&gt;navigation#displayLoading     turbo:before-fetch-response@document-&gt;navigation#displayContent&amp;#x22;&gt;  &lt;!-- ... --&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If we were to refresh the page and click around (and set the Network speed to slow), we’d see a flash of the loading
state, followed by the details of the movie.&lt;/p&gt;
&lt;img alt=&quot;Click on details on a slow connection and user will see the loading indicator&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;480&quot; height=&quot;266&quot; src=&quot;https://www.mikewilson.dev/_astro/click-to-load-with-spinner.CviyYL9Z_Z1fAbyf.webp&quot; &gt;
&lt;p&gt;If we wanted to really spice things up, we can incorporate RxJs to better handle the loading state and not show the
loading state unless the request takes a certain amount of time. Check out my
&lt;a href=&quot;https://www.mikewilson.dev/posts/stimulus-and-rxjs-loading-state/&quot;&gt;previous post&lt;/a&gt; to see how to do that.&lt;/p&gt;
&lt;h3 id=&quot;selected-item&quot;&gt;&lt;a class=&quot;not-prose&quot; href=&quot;#selected-item&quot;&gt;Selected Item&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we have a loading state implemented, we want to let the user know in the index frame which item in the detail
frame is being used. Unfortunately, there doesn’t seem to be an easy way to do this using the &lt;code&gt;before-fetch-request&lt;/code&gt;
event, as we don’t know the specific item being clicked. However, there’s another event which might be even better for
us: &lt;code&gt;turbo:click&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;turbo:click&lt;/code&gt; event is fired on the element that was clicked to trigger navigation. Let’s update our template to use
this instead:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;erb&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#1E7734&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movies&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-controller&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;navigation&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;     &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--0fs:italic;--1:#6F42C1&quot;&gt;data-action&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;turbo:before-fetch-response@document-&gt;navigation#displayContent&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; turbo_frame_tag &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;:index&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;class:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-cards&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#24292E&quot;&gt;@movies&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; |movie| &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; link_to &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;movie_path&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(movie[&lt;/span&gt;&lt;span style=&quot;--1:#005CC5&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#BD93F9&quot;&gt;slug&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;]),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;data:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;turbo_frame:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;action:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;turbo:click-&gt;navigation#displayLoading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;          &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;navigation_target:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--1:#005CC5&quot;&gt;class:&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;movie-card&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;&amp;#x3C;!-- ... --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#24292E&quot;&gt;&amp;#x3C;%&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--1:#24292E&quot;&gt;&lt;span style=&quot;--0:#F8F8F2&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&lt;div class=&amp;#x22;movies&amp;#x22; data-controller=&amp;#x22;navigation&amp;#x22;     data-action=&amp;#x22;turbo:before-fetch-response@document-&gt;navigation#displayContent&amp;#x22;&gt;  &lt;%= turbo_frame_tag :index, class: &amp;#x22;movie-cards&amp;#x22; do %&gt;    &lt;% @movies.each do |movie| %&gt;      &lt;%= link_to movie_path(movie[:slug]),        data: {          turbo_frame: &amp;#x22;details&amp;#x22;,          action: &amp;#x22;turbo:click-&gt;navigation#displayLoading&amp;#x22;,          navigation_target: &amp;#x22;link&amp;#x22;        },        class: &amp;#x22;movie-card&amp;#x22; do %&gt;        &lt;!-- ... --&gt;      &lt;% end %&gt;    &lt;% end %&gt;  &lt;% end %&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;We’ve done a few things here. We removed one of our document level listeners, added a &lt;code&gt;turbo:click&lt;/code&gt; action handler on
the actual link itself and also added a &lt;code&gt;data-navigation-target&lt;/code&gt; to tell the Stimuls controller about our link elements.
We can now update our Stimulus controller to handle the event and new target.&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;default&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#8BE9FD;--0fs:italic;--1:#6F42C1&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#AE4B07&quot;&gt;targets&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;loading&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayLoading&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.loadingTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.contentTarget.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;hidden&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; value &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; event.detail.url;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;updateLinks&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(value);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;displayContent&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// Iterate all of our links, remove the selected class, but then add&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#96A1C2;--1:#616972&quot;&gt;// if the link href matches the url we&apos;re navigating to.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;updateLinks&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;item&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#BD93F9;--0fs:italic;--1:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;.linkTargets.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;--0:#FFB86C;--0fs:italic;--1:#AE4B07&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;link.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;selected&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; (link.href &lt;/span&gt;&lt;span style=&quot;--0:#FF79C6;--1:#BF3441&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt; item) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;link.classList.&lt;/span&gt;&lt;span style=&quot;--0:#50FA7B;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--1:#032F62&quot;&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--0:#F1FA8C&quot;&gt;selected&lt;/span&gt;&lt;span style=&quot;--0:#E9F284&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F8F8F2;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;export default class extends Controller {  static targets = [&amp;#x22;content&amp;#x22;, &amp;#x22;loading&amp;#x22;, &amp;#x22;link&amp;#x22;];  displayLoading(event) {    this.loadingTarget.classList.remove(&amp;#x22;hidden&amp;#x22;);    this.contentTarget.classList.add(&amp;#x22;hidden&amp;#x22;);    let value = event.detail.url;    this.updateLinks(value);  }  displayContent() {    // ...  }  // Iterate all of our links, remove the selected class, but then add  // if the link href matches the url we&amp;#x27;re navigating to.  updateLinks(item) {    this.linkTargets.forEach((link) =&gt; {      link.classList.remove(&amp;#x22;selected&amp;#x22;);      if (link.href === item) {        link.classList.add(&amp;#x22;selected&amp;#x22;);      }    });  }}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the &lt;code&gt;turbo:click&lt;/code&gt; event is fired, the url that we’re navigating to is included as part of the CustomEvent detail.
We can use that to then iterate through our list of movie links, and set the &lt;code&gt;selected&lt;/code&gt; class on that particular item.
With this in place, if we refresh the page and start navigating around we’ll see that the movie being displayed is now
highlighted in the index.&lt;/p&gt;
&lt;img alt=&quot;Final product&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;640&quot; height=&quot;354&quot; src=&quot;https://www.mikewilson.dev/_astro/final-movie-click-throughs.DSEELSat_ZYU5NF.webp&quot; &gt;
&lt;p&gt;With the Hotwire tools, such as Turbo and Stimulus, it’s really impressive how far we can get with such little
Javascript. As DHH would say, “look at all the code I’m not writing”.&lt;/p&gt;
&lt;p&gt;If you’d like to see the final code, please feel free to browse
the &lt;a href=&quot;https://github.com/mike1o1/hotwired-spa-experience&quot; rel=&quot;noreferrer noopener&quot; target=&quot;_blank&quot;&gt;mike1o1/hotwired-spa-experience&lt;/a&gt; repository.&lt;/p&gt;</content:encoded></item></channel></rss>