<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>OnRailsBlog</title>
    <link>https://onrails.blog</link>
    <language>en</language>
    <lastBuildDate>Tue, 02 Jun 2026 00:41:23 +0000</lastBuildDate>
    <atom:link href="https://onrails.blog/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Interactive Modals</title>
      <link>https://onrails.blog/2024/06/01/interactive-modals</link>
      <guid isPermaLink="true">https://onrails.blog/2024/06/01/interactive-modals</guid>
      <pubDate>Sat, 01 Jun 2024 14:58:15 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>There is a lot of talk about modals in Rails, and this tutorial shows you how with some Stimulus sprinkles, you can have very interactive modals.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/VKJ_1qoPTsE","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/VKJ_1qoPTsE
</div></figure>
<!-- /wp:embed -->

<!-- wp:paragraph -->
<p>The code for the demo can be found on Github: https://github.com/OnRailsBlog/interactive_modal</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":3} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Headless UI with StimulusJS and an Outlet</title>
      <link>https://onrails.blog/2024/05/08/headless-ui-with-stimulusjs-and-an-outlet-screencast</link>
      <guid isPermaLink="true">https://onrails.blog/2024/05/08/headless-ui-with-stimulusjs-and-an-outlet-screencast</guid>
      <pubDate>Wed, 08 May 2024 15:09:37 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Headless UI 2.0 just came out from TailwindLabs. I often find myself using their components in my projects, and I wanted to show you my process for converting their React components to use Stimulus, since I haven’t been using React in any of my projects.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/fvm0F_f5oBA","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/fvm0F_f5oBA
</div></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Switching on Interactivity</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The component I wanted to use was the <a href="https://headlessui.com/react/switch">Switch</a>. It’s styled after the native iOS switch toggle, where you turn an option on or off. Tapping anywhere on the button will transition the inner circle from one side to the other. The version of this switch uses CSS animations, and all our controller would need to do is update a <code>data-checked</code> attribute on the button, and CSS will animate the sliding action. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>First, I copied the HTML from the rendered React component, and you get:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;button 
    class="group relative flex h-7 w-14 cursor-pointer rounded-full bg-blue-900/10 p-1 transition-colors duration-200 ease-in-out focus:outline-none data-[focus]:outline-1 data-[focus]:outline-white data-[checked]:bg-blue-900/30" 
    role="switch" 
    type="button" 
    tabindex="0" 
    aria-checked="true"
	data-checked&gt;
    &lt;span aria-hidden="true" class="pointer-events-none inline-block size-5 translate-x-0 rounded-full bg-white ring-0 shadow-lg transition duration-200 ease-in-out group-data-[checked]:translate-x-7"&gt;&lt;/span&gt;
  &lt;/button&gt;
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Let’s generate a Stimulus controller called <code>switch</code> that will handle the clicks for us:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ ./bin/rails g stimulus switch 
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Add the controller and an action to the <code>&lt;button&gt;</code> tag:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>data-controller="switch"
data-action="switch#toggle"
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Then when the button is clicked, it will fire the toggle action on the controller:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  toggle() {
    if (this.element.dataset.checked) {
      delete this.element.dataset.checked;
      this.element.ariaChecked = false;
    } else {
      this.element.dataset.checked = true;
      this.element.ariaChecked = true;
    }
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now when you click the button, the switch toggles back and forth. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Adding an Outlet for the Switch</h2>
<!-- /wp:heading -->

<!-- wp:image {"id":1142,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA3NiwicHVyIjoiYmxvYl9pZCJ9fQ==--4e6b2dbcc91c849f185d4dd82ba70add4925f059/Headless-UI-Switch-Outlet.gif" alt="" class="wp-image-1142"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>This switch just exists on its own right now. It would be great if it could actually update a value on a form. We can fix this with <a href="https://stimulus.hotwired.dev/reference/outlets">Stimulus Outlets</a>. This ability allows our controller to talk to another controller on the page. Stimulus uses the controller name and a CSS path to connect to the correct element on the page. We start by adding the input checkbox that we want to access: </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;input type="checkbox" id="todo-complete" data-controller="input"&gt;
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Then we add a data attribute to the <code>switch</code> controller:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>data-switch-input-outlet="#todo-complete"
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>In the Stimulus controller, we add the outlet declaration:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>static outlets = ["input"];
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now, we can access the <code>inputOutletElement</code> in the <code>switch</code> controller, and set it to checked, and read its value. You can update the <code>toggle()</code> method:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  toggle() {
    this.inputOutletElement.checked = !this.inputOutletElement.checked;
    if (this.inputOutletElement.checked) {
      this.element.dataset.checked = true;
      this.element.ariaChecked = true;
    } else {
      delete this.element.dataset.checked;
      this.element.ariaChecked = false;
    }
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">In Closing</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Headless UI is great for some prebuilt components using Tailwind CSS. Even though they’re built in React, we can use Stimulus to add some interactivity. Stimulus Outlets allow us to target elements on the page outside the hierarchy of the controller. I can’t wait to use this more in my applications.</p>
<!-- /wp:paragraph -->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Animate Filtering Data in HOTWire</title>
      <link>https://onrails.blog/2024/04/23/animate-filtering-data-in-hotwire</link>
      <guid isPermaLink="true">https://onrails.blog/2024/04/23/animate-filtering-data-in-hotwire</guid>
      <pubDate>Tue, 23 Apr 2024 17:30:18 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>We can progressively enhance the filtering search. In the <a title="https://onrails.blog/2024/04/09/filtering-data-in-hotwire/(opens in a new tab)" href="https://onrails.blog/2024/04/09/filtering-data-in-hotwire/">previous tutorial</a>, we saw how easy it is to use Turbo 8’s morphing and a very simple Stimulus controller to trigger the request back to the server. We can dig deeper into the events and get a very satisfying animation of Todos disappearing when they’re filtered out, and reappearing when they are back in the list.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/cDZ1gTEnABI","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/cDZ1gTEnABI
</div></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Listening for Morphing</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Our Stimulus controller is going to get some new methods. It will hook into the Turbo event stream. The first change is to add the <code>replace</code> option to the Turbo visit in the controller. This will effectively make Turbo morph the new changes on the page <sup><a id="ffn1" href="#fn1" class="footnote">1</a></sup>. We want morphing, because we can listen for the upcoming changes, and tweak the behavior for elements that are changing. This will fire the morphing events.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Animating removals</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Removing items is easier, since we get the items that are going to be removed from the page, and we tell Turbo <em>not to remove them</em>. Now, we handle the removal, after a pleasing animation.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Add a new action to the Search controller: <code>turbo:before-morph-element@document-&gt;search#animateRemovals</code>. This will be called every time an element inside the page is going to change, so I added a data attribute to the Todo items to flag that they should be animated away: <code>data-animate-exit="true" </code>. The <code>animateRemovals</code> method in the Search controller will check that this property exists.  <a href="https://turbo.hotwired.dev/reference/events#turbo%3Abefore-morph-element"><code>turbo:morph-element</code></a> will have a property called <code>newElement</code> if the element getting replaced. Since the Todos are getting filtered, and will no longer be on the page, the <code>newElement</code> will be null. Once we have the element to animate away, calling <code>preventDefault()</code> on the event will cancel the element form being removed. Our controller takes over the responsibility for removing the element by calling a <code>animateExit()</code> function that can be tweaked for the preferred animation style.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>
  animateRemovals(event) {
    if (event.detail.newElement == null &amp;&amp; event.target.dataset.animateExit) {
      event.preventDefault();
      this.animateExit(event.target);
    }
  } 

  async animateExit(target) {
    target.animate(
      [
        {
          transform: `scale(1, 1)`,
          transformOrigin: "center",
          height: "auto",
          opacity: 1.0,
        },
        {
          transform: `scale(0.9, 0.7)`,
          opacity: 0.2,
          height: "80%",
          offset: 0.8,
        },
        {
          transform: `scaleY(0.8, 0)`,
          transformOrigin: "center",
          height: 0,
          opacity: 0,
        },
      ],
      {
        duration: 75,
        easing: "ease-out",
      }
    );
    await Promise.all(
      target.getAnimations().map((animation) =&gt; animation.finished)
    );
    target.remove();
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The animation in this case uses keyframes to collapse and shrink the Todo as its opacity changes. Play around with what makes sense for your application. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Animating Insertions</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Animating new elements is trickier. It requires listening for the <a href="https://turbo.hotwired.dev/reference/events#turbo%3Amorph-element"><code>turbo:morph-element</code></a> event, which fires after all the changes have been made on the page, but before the page is redrawn. This allows us to make some visual changes to the new Todo elements. The challenge is figuring out which elements need to be animated in. In the case of this event, <code>event.details.newElement</code> refers to the old list, and <code>event.target</code> refers to the list in the dom. First, it goes through the old list to collect all the existing Todo ids. Then it goes through the new list to find ids that are new, and then it animates each of those new Todos.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  animateInsertions(event) {
    if (
      event.target.childNodes.length &gt; event.detail.newElement.childNodes.length
    ) {
      var existingIds = new Set();
      for (var i = 0; i &lt; event.detail.newElement.childNodes.length; i++) {
        let child = event.detail.newElement.childNodes[i];
        if (
          child.nodeName != "#text" &amp;&amp;
          child.dataset.animateEntrance &amp;&amp;
          child.id != null
        ) {
          existingIds.add(child.id);
        }
      }
      for (var i = 0; i &lt; event.target.childNodes.length; i++) {
        let child = event.target.childNodes[i];
        if (
          child.nodeName != "#text" &amp;&amp;
          child.dataset.animateEntrance &amp;&amp;
          child.id != null &amp;&amp;
          !existingIds.has(child.id)
        ) {
          this.animateEntrance(child);
        }
      }
    }
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The animation first sets the height to 0, and shrinks the Todo, and animates the it expanding to fit the size of its original view. Make sure you play around with these different values depending on what you need for your application.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  async animateEntrance(target) {
    target.animate(
      [
        {
          transform: `scale(0.8, 0.0)`,
          transformOrigin: "center",
          height: "0",
          opacity: 0.0,
        },
        {
          transform: `scale(0.9, 0.7)`,
          opacity: 0.2,
          height: "80%",
          offset: 0.2,
        },
        {
          transform: `scale(1, 1)`,
          transformOrigin: "center",
          height: "auto",
          opacity: 1,
        },
      ],
      {
        duration: 100,
        easing: "ease-out",
      }
    );
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Final enhancement</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>As I was playing with the searching, I realized I don’t necessarily want to hint to the reordering features. I found hiding the buttons was a visual indicator that you may not want to do that. What’s neat is using CSS to hide the buttons when there is text in the search field.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>#search:not(:placeholder-shown) + div {
  .todo &gt; div &gt; button.up,
  .todo &gt; div &gt; button.down {
    animation-duration: .2s;
    animation-name: fadeOut;
    animation-timing-function: ease-in-out;
    animation-fill-mode: forwards;
  }
}

@keyframes fadeOut {

  0% {
    transform: scale(1);
    transform-origin: center;
    height: "auto";
    opacity: 1.0;
  }

  20% {
    transform: scale(1.2);
    transform-origin: center;
    height: "auto";
    opacity: 0.8;
  }

  100% {
    transform: scale(0);
    transform-origin: center;
    height: 0;
    opacity: 0;
    visibility: hidden;
  }
}
</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Interactive Apps</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>It’s quite possible, and very straightforward to add nice animations as enhancements of our existing applications with just a little Stimulus and the amazing Javascript animations the browsers all support.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://github.com/OnRailsBlog/todo_app/tree/5d01750681fed0794d11dd184cb8688a3cdf332f">You can find the source code at this point on Github</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":3} /-->

<!-- wp:list {"ordered":true} -->
<ol><!-- wp:list-item -->
<li><a href="https://turbo.hotwired.dev/handbook/drive#application-visits">https://turbo.hotwired.dev/handbook/drive#application-visits</a> <a href="#ffn1">↩</a></li>
<!-- /wp:list-item --></ol>
<!-- /wp:list -->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Filtering Data in HOTWire</title>
      <link>https://onrails.blog/2024/04/09/filtering-data-in-hotwire</link>
      <guid isPermaLink="true">https://onrails.blog/2024/04/09/filtering-data-in-hotwire</guid>
      <pubDate>Tue, 09 Apr 2024 14:02:12 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>When we have a long list of Todos, sometimes we want to filter them by name. We can easily do this using Turbo’s morphing and a Stimulus controller to update the page from the server.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/MNKb1Xbu298","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/MNKb1Xbu298
</div></figure>
<!-- /wp:embed -->

<!-- wp:paragraph -->
<p>One previous way to get this interactivity was to use a Stimulus controller that filtered the HTML. This still works, and might be a strategy depending on your situation. This technique will send the request to the server, and leverage the Database to perform the filtering. This might work better if you have pagination,  or don’t want to load hundreds or thousands of records onto a page to perform filtering.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Filtering on the Server</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Start by updating <code>todos_controller.rb</code> and the <code>index</code> action. The controller should have default query of the Todos:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>@todos = Todo.all.order("priority")</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We will look for a query parameter called <code>:todo</code> that we’ll use to filter the name of the Todo. The action checks for the presence of the parameter, and that it isn’t blank. It lowercases the param, and then performs a lower case query of all the names in the Todo.  If the todo parameter is missing, it pulls in all the Todos. It then sorts the values by priority.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>    query = if params[:todo] &amp;&amp; !params[:todo].nil? &amp;&amp; !params[:todo].blank?
      name = "%#{params[:todo].downcase.strip}%"
      Todo.where("lower(name) like ?", name)
    else
      Todo.all
    end
    @todos = query.order("priority")
</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">The Stimulus Search controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>You can generate a new controller we’ll call search:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ ./bin/rails g stimulus search</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This controller will have a parameter value for the url to visit, and a single action, <code>search</code>.  When the search action triggers, it will read the name and value from the target that fired the event, append that to the <code>urlValue</code>, and tell Turbo to visit the page. Turbo will make the request, and perform the morphing. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="search"
export default class extends Controller {
  static values = { url: String };

  search(event) {
    Turbo.visit(`${this.urlValue}?${event.target.name}=${event.target.value}`);
  }
}</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Wiring up the HTML</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>First, we can add an <code>input</code> field for our search controller.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  &lt;input 
    id="search" 
    type="text" 
    name="todo" 
    value="&lt;%= params[:todo] %&gt;"
    placeholder="Search" 
    data-controller="search" 
    data-search-url-value="&lt;%= request.path %&gt;"
    data-action="search#search"
    data-turbo-permanent
    class="w-full my-1 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"&gt;    
  &lt;!-- existing todos div --&gt;
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The action will call to our Stimulus controller, and fetch the updated results. The form gets set to whatever was passed, so that the page refreshes have the correct information in the search field. The input field is also set as <code>data-turbo-permanent</code> so that it keeps focus when the server results come back, and the page is morphed. If it wasn’t there, the text field would lose focus after every keystroke, which provides for a poor user experience.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now you have a simple way to filter data on your page.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":3} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>HOTWire &amp; Turbo Tutorial: Animated Deletions and Insertions</title>
      <link>https://onrails.blog/2024/04/01/hotwire-turbo-tutorial-animated-deletions-and-insertions</link>
      <guid isPermaLink="true">https://onrails.blog/2024/04/01/hotwire-turbo-tutorial-animated-deletions-and-insertions</guid>
      <pubDate>Mon, 01 Apr 2024 18:54:06 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>With the addition of the new Todo form appearing at the bottom of the Todos, and the delete action removing a Todo, we have a very functional app. It would be nice if those additions and removals had a little animation to emphasize what’s happening on the page. If there was a long list, we might miss the deletion, especially if a network request caused a delay in the removal of the Todo. We can hook into Turbo streams, and run some animations on these actions to make them appear and disappear.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/OY_YraWRXds","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/OY_YraWRXds
</div><figcaption class="wp-element-caption">Not animated vs. animated</figcaption></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Adding a Custom Turbo Action</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Turbo allows for the addition of <a href="https://turbo.hotwired.dev/handbook/streams#custom-actions">custom actions</a> to enhance the experience. Each StreamAction is passed a <a href="https://github.com/hotwired/turbo/blob/600203edf6a7fdba328bfbc9ca8c62354c7d3a27/src/elements/stream_element.js#L4">StreamElement</a>, which contains the Turbo Frame that was sent from the server. We’re going to add two action which we’ll call <code>animated_append</code> and <code>animated_remove</code> to match the <code>append</code> and <code>remove</code> actions. The animated version of the remove action will <a href="https://github.com/hotwired/turbo/blob/600203edf6a7fdba328bfbc9ca8c62354c7d3a27/src/core/streams/stream_actions.js#L23">perform the same removal</a>, but after some animations run that scale and change the height of the removed element. These animations could be customized depending on the design of the web app. The animated append is a little trickier, but <a href="https://github.com/hotwired/turbo/blob/600203edf6a7fdba328bfbc9ca8c62354c7d3a27/src/core/streams/stream_actions.js#L9">it first adds the elements</a>, and then immediately changes the height to 0 to height it, and then animates the scaling. Add the actions in <code>application.js</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">Turbo.StreamActions.animated_remove = async function () {
  this.targetElements.forEach(async (target) =&gt; {
    target.animate(
      [
        {
          transform: `scale(1)`,
          transformOrigin: "top",
          height: "auto",
          opacity: 1.0,
        },
        {
          transform: `scale(0.8)`,
          opacity: 0.2,
          height: "80%",
          offset: 0.8,
        },
        {
          transform: `scale(0)`,
          transformOrigin: "top",
          height: 0,
          opacity: 0,
        },
      ],
      {
        duration: 75,
        easing: "ease-out",
      }
    );
    await Promise.all(
      target.getAnimations().map((animation) =&gt; animation.finished)
    );
    target.remove();
  });
};

Turbo.StreamActions.animated_append = async function () {
  this.removeDuplicateTargetChildren();
  this.targetElements.forEach(async (target) =&gt; {
    target.append(this.templateElement.content);
    target.lastElementChild.animate(
      [
        {
          transform: `scaleY(0.0)`,
          transformOrigin: "top",
          height: "0",
          opacity: 0.0,
        },
        {
          transform: `scale(0.8)`,
          opacity: 0.2,
          height: "80%",
          offset: 0.2,
        },
        {
          transform: `scaleY(1)`,
          transformOrigin: "top",
          height: "auto",
          opacity: 1,
        },
      ],
      {
        duration: 100,
        easing: "ease-out",
      }
    );
  });
};
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Sending the frame actions </h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The next bit is to send the frames from the server with those actions. An animated append of the new Todo form can be as simple as:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;turbo-stream action="animated_append" target="todos"&gt;
  &lt;template&gt;
	&lt;%= turbo_frame_tag 'new_todo', target: "_top", class: "flex items-center py-3 pl-3 bg-white border-b pr-11 gap-x-4 todo" do %&gt;
    &lt;%= render partial: 'form', locals: { todo: @todo } %&gt;
  &lt;% end %&gt;
  &lt;/template&gt;
&lt;/turbo-stream&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And removing a Todo from the list could be this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;turbo-stream action="animated_remove" target="todo_101"&gt;
  &lt;template&gt;&lt;/template&gt;
&lt;/turbo-stream&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>action</code> field on the Turbo Stream needs to correspond to the name of the actions we added in <code>application.js</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Adding helpers in Rails</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We can add custom helpers in our Rails app to make those templates available. Add a helper file in <code>app/helpers/</code> called <code>turbo_streams_actions_helper.rb</code>. We’ll append two actions that will mirror the <a href="https://github.com/hotwired/turbo-rails/blob/102a491754d46f7dd924201fcfaf879a0f04b11c/app/models/turbo/streams/tag_builder.rb"><code>TagBuilder</code> in Turbo Rails</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">module TurboStreamsActionsHelper
  module CustomTurboStreamActions
    def animated_remove(target)
      action :animated_remove, target, allow_inferred_rendering: false
    end

    def animated_append(target, content = nil, **, &amp;block)
      action(:animated_append, target, content, **, &amp;block)
    end
  end

  Turbo::Streams::TagBuilder.prepend(CustomTurboStreamActions)
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>We can use the actions like any other Turbo Stream action. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><code>&lt;%= turbo_stream.append "todos" do %&gt;</code> becomes </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><code>&lt;%= turbo_stream.animated_append "todos" do %&gt;</code> in <code>app/views/todos/new.html.erb</code></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>And in <code>todos_controller.rb</code>, the destroy action changes from <code>format.turbo_stream { render turbo_stream: turbo_stream.remove(@todo) }</code> to <code>format.turbo_stream { render turbo_stream: turbo_stream.animated_remove(@todo) }</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Progressively Enhancing </h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The neat bit about this change is that we don’t need to completely rewrite the partials to get animations as the page changes. We go from portions of the page the appear suddenly or leave suddenly to a pleasant coming and going look to help the app feel more alive, and more similar to what you’d expect in a mobile application.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>HOTWire: Where do I store my HTML state?</title>
      <link>https://onrails.blog/2024/03/26/hotwire-where-do-i-store-my-html-state</link>
      <guid isPermaLink="true">https://onrails.blog/2024/03/26/hotwire-where-do-i-store-my-html-state</guid>
      <pubDate>Tue, 26 Mar 2024 13:58:48 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>We’re used to storing all of our data in the database, and letting Active Record pull it out, Action View to format it, and Action Controller to manage the request and response. But when we want quick client side interactivity, sometimes we need some extra data annotations on the HTML side that we can use without needing to communicate with the server. Most of this data is used by Stimulus, but Turbo has a few tags that can be useful.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/9wf9yXVJ2d8","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/9wf9yXVJ2d8
</div></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading"><code>&lt;head&gt;</code> State</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Other Javascript frameworks, like Vue.js, React, and Angular, typically generate HTML on the client side, in Javascript. We can use the fact we’re sending server generated HTML Over The Wire to store information we might need in the <code>'&lt;head&gt;</code> tag of the page. Turbo will keep these values in sync as each page loads. Rails also has a <code>provides</code> helper that can be used insert a tag into the head of the page. For example, if an <code>account-id</code> should be in the head, you can use this anywhere in a view:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;% provide :head, tag.meta(name: "account-id", content: "1") %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>If your layout, like <code>application.html.erb</code> has this line:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= yield :head %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The account id meta tag will look like:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;meta name="account-id" content="1"&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now, in a Stimulus controller, you could add a function like:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">function getMetaValue(name) {
  const element = document.head.querySelector(`meta[name="${name}"]`);
  return element.getAttribute("content");
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This will help read that value from the head, and you can include it wherever you want: </p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">let accountId =  getMetaValue("account-id")</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Controller State</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The Stimulus controller is a Javascript object, so properties can be set on the editor. You may have be using a third party library to create an object and keep it around during the controller’s life. For example, if you’re using <a href="https://zurb.github.io/tribute/example/">Tribute</a>to add mentions in a textfield, you can use <code>this</code> and store it to a variable:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">connect() {
  this.tribute = new Tribute({
    collection: []
  });
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And in a different method, you can access the <code>tribute</code> variable:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">addNames() {
  this.tribute.appendCurrent([
    { name: "Howard Johnson", occupation: "Panda Wrangler", age: 27 },
    { name: "Fluffy Croutons", occupation: "Crouton Fluffer", age: 32 }
  ]);
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Data Attributes</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Stimulus provides two mechanisms to make data available for your controller. </p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Values properties</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The first is the <a title="Values in the Stimulus Handbook" href="https://stimulus.hotwired.dev/reference/values"><code>values</code></a> properties. These allow you to set parameters for the controller. For example, if you had a toggle like you would find in iOS, you may have an <code>enabled</code> attribute on it. </p>
<!-- /wp:paragraph -->

<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:image {"id":1091,"linkDestination":"none","align":"center"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA0OCwicHVyIjoiYmxvYl9pZCJ9fQ==--d50839325b15463307bad434765782dbd3f2513c/DraggedImage-6.png" alt="Button Toggled Off" class="wp-image-1091"></figure>
<!-- /wp:image --></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column"><!-- wp:image {"id":1090,"linkDestination":"none","align":"center"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA0MiwicHVyIjoiYmxvYl9pZCJ9fQ==--22c0fe8ebd3eabbfd65a051d6c14b0909143aa89/DraggedImage-1-1.png" alt="Button Toggled On" class="wp-image-1090"></figure>
<!-- /wp:image --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->

<!-- wp:paragraph -->
<p>If the controller is named <code>toggle</code>, then adding a data attribute <code>enabled</code> looks like this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code"> &lt;button type="button" 
    data-controller="toggle" 
    data-toggle-enabled-value="true" 
...</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>In the controller, you add the <code>values</code> target, and can access it anywhere in the controller:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">// Connects to data-controller="toggle"
export default class extends Controller {
  static values = { enabled: Boolean };

  connect() {
    if (this.enabledValue) {
      console.log("This is enabled");
    }
  }
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Classes properties</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Since changing classes is a common interactive enhancement, controllers often need to toggle classes. The <a href="https://stimulus.hotwired.dev/reference/css-classes"><code>classes</code></a> properties assumes the values are strings, and will split apart multiple classes if they’re separated by a space. The button toggle example above needs four different classes to change, for the background color and the movement of the circle. Add these classes in the HTML:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">    data-toggle-enabled-class="bg-blue-600"
    data-toggle-disabled-class="bg-gray-200"
    data-toggle-enabled-translate-class="translate-x-5"
    data-toggle-disabled-translate-class="translate-x-0"</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And then they’re accessible in the controller:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">  static classes = [
    "enabled",
    "enabledTranslate",
    "disabled",
    "disabledTranslate",
  ];</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>They can be used in a method when you want to swap out classes:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">  setEnabled() {
    this.frameTarget.classList.add(this.enabledClass);
    this.frameTarget.classList.remove(this.disabledClass);
    this.circleTarget.classList.add(this.enabledTranslateClass);
    this.circleTarget.classList.remove(this.disabledTranslateClass);
  }</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Action Params</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Actions can also <a href="https://stimulus.hotwired.dev/reference/actions#action-parameters">pass along values as parameters</a> to the method that’s called. The toggle button has a <code>switch</code> method that’s called when button is clicked. Adding an attribute, such as an id, could become available to the switch method:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">data-action="toggle#switch"
data-toggle-id-param="123"</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>When the action fires, the params are added to the event:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code"> switch(event) {
    console.log(event.params.id);
  }</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Data everywhere</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>There are lots of options for passing data along from the server to the front end, and making it available for our Stimulus controllers. Our website can be interactive without the need for dipping into a heavier Javascript framework.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":3} /-->

<!-- wp:woocommerce/handpicked-products {"products":[1016,876,1041]} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>HOTWire: Considering Morphing or Turbo Frames</title>
      <link>https://onrails.blog/2024/03/26/hotwire-considering-morphing-or-turbo-frames</link>
      <guid isPermaLink="true">https://onrails.blog/2024/03/26/hotwire-considering-morphing-or-turbo-frames</guid>
      <pubDate>Tue, 26 Mar 2024 09:00:00 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:image {"id":1007,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA1NiwicHVyIjoiYmxvYl9pZCJ9fQ==--6fa89d39b2fffff319a07e2473ef631d169814b8/HOTWire-Frame-New-Edit-Delete-1024x564.png" alt="" class="wp-image-1007"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>With the new morphing features in Turbo 8, you now need to decide on when to use Turbo streams or Turbo frames instead of full page refreshing. Thankfully, all three techniques work together. Let’s take the Todo app that we’ve been working on, and see where using Streams or Frames makes sense.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/y08mnpYrDmA","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/y08mnpYrDmA
</div></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Turbo Streams for a new Todo form</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The current new Todo interaction is a new page when clicking the <code>New Todo</code> button on the top of the page. If we add the <code>data-turbo-stream</code> attribute, <a href="https://turbo.hotwired.dev/handbook/streams#streaming-from-http-responses">the <code>GET</code> request now appears to the server as a <code>TURBO_STREAM</code> request</a>. The response, <code>new.turbo_stream.erb</code> can append the form to the bottom of the list of Todos, and then when the form is submitted, the full page refresh will display the new Todo in the same place. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>First, add the <code>data-turbo-stream</code> to the <code>New Todo</code> link on <code>todos\index.html.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= link_to "New todo", new_todo_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium", data: {turbo_stream: true} %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then add the file <code>todos\new.turbo_stream.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= turbo_stream.append "todos" do %&gt;
  &lt;%= turbo_frame_tag 'new_todo', target: "_top", class: "flex items-center py-3 pl-3 bg-white border-b pr-11 gap-x-4 todo" do %&gt;
    &lt;%= render partial: 'form', locals: { todo: @todo } %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This will append the new form to the bottom of the Todos list. The <code>target</code> is set to <code>_top</code> so that the successful creation and redirection bring the page back to the index action. Otherwise, since there wouldn’t be a Turbo Frame named <code>new_todo</code>, Turbo wouldn’t have anything to replace the interior content.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":1000,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA0OSwicHVyIjoiYmxvYl9pZCJ9fQ==--b572c93f6bd16e2cf870bafd4953e1431e7361b2/DraggedImage.png" alt="" class="wp-image-1000"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Now we are using Turbo Streams to load a new form. The form, <code>todos\_form.html.erb</code>, should be changed to fit inline:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= form_with(model: todo, class: "flex-1 flex flex-row gap-x-4 items-center") do |form| %&gt;
  &lt;%= form.check_box :completed, class: "ml-8" %&gt;
  &lt;div class="flex-1"&gt;
    &lt;%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 w-full" %&gt;
  &lt;/div&gt;
  &lt;div class="inline"&gt;
    &lt;%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And you get a nice new Todo form. Clicking the Create Todo button saves the Todo, performs a redirect to the <code>Todos#index</code>, which Turbo morphs and shows the new Todo at the bottom of the list.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":1003,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--72b124d3cb3ec4499b60198cbf66825c41607662/DraggedImage-1.png" alt="" class="wp-image-1003"></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Editing in place</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Using Turbo Frames, we don’t have to make many other changes to get in place editing of each Todo. First, change the <code>div</code> of each Todo in <code>todos\_todo.html.erb</code> to a <code>turbo_frame_tag</code>. The original:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;div id="&lt;%= dom_id todo %&gt;" 
  class="flex items-center py-3 bg-white border-b gap-x-4 todo" 
  data-reorderable-id="&lt;%= todo.id %&gt;" 
  data-reorderable-path="&lt;%= todo_priority_path(todo) %&gt;"
  draggable="true"&gt;
  	&lt;!-- rest of partial --&gt;
&lt;/div&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>becomes:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= turbo_frame_tag todo, class: "flex items-center py-3 bg-white border-b gap-x-4 todo", data: { reorderable_path: todo_priority_path(todo), reorderable_id: todo.id }, draggable: true do %&gt;
	&lt;!-- rest of partial --&gt;
&lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The next change is to update the <code>todos\edit.html.erb</code> partial to wrap the form in a <code>turbo_frame_tag</code>. Change the following lines:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">    &lt;%= render "form", todo: @todo %&gt;
    &lt;%= link_to "Show this todo", @todo, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And wrap the form like this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">    &lt;%= turbo_frame_tag @todo, class: "flex items-center py-3 bg-white border-b gap-x-4 todo"  do %&gt;
      &lt;%= turbo_stream_from @todo %&gt;
      &lt;%= render "form", todo: @todo %&gt;
      &lt;%= link_to "Back", @todo, class: "mr-10 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %&gt;
    &lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Clicking the “Edit” button will send a request for the <code>edit.html.erb</code>. Turbo will just get the contents of the frame, and replace the inside with the form. Clicking “Back” will make a request to the <code>show.html.erb</code>, which has the same frame id since it renders the <code>_todo.html.erb</code> partial. </p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":1002,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA0NCwicHVyIjoiYmxvYl9pZCJ9fQ==--42f5dd41b40a1b833d403fb51a9516973f20d193/DraggedImage-2.png" alt="" class="wp-image-1002"></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Deleting Todos</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Lastly, we can use Turbo actions to remove a Todo from the list. Add a delete button to the <code>_todo.html.erb</code> partial:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= link_to todo_path(todo), data: {turbo_method: "delete", turbo_confirm: "Are you sure?" }, class: "rounded-full p-2 ml-2 bg-red-500 inline-block font-medium mr-1 text-red-50" do %&gt;
    &lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6"&gt;
      &lt;path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" clip-rule="evenodd" /&gt;
    &lt;/svg&gt;
  &lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This uses Heroicons trash icon and you get a nice delete button.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":1001,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA0NSwicHVyIjoiYmxvYl9pZCJ9fQ==--95a483d4f4859e0ee69d770581e91c239d02cb78/DraggedImage-3.png" alt="" class="wp-image-1001"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Clicking the trashcan will send the request to the <code>destroy</code> method on the <code>TodosController</code>, so add a new response in the <code>respond_to</code> block:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">format.turbo_stream { render turbo_stream: turbo_stream.remove(@todo) }
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This will send a response back that removes the Todo from the list.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Putting it all together</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Turbo Morphs fits right on top of how an existing app works. It layers on a new performance feel without needing to rewrite the who app to work with a new feature. That’s the best kind of upgrade.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://github.com/OnRailsBlog/todo_app/tree/8cea7e4d39a23d93ec50391f922baff0b7cf2fea" target="_blank" rel="noreferrer noopener">You can find the source code on Github here</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Animating the Insertions and Deletions</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>You can animate the additions and removals from the page easily. <a href="https://onrails.blog/2024/04/01/hotwire-turbo-tutorial-animated-deletions-and-insertions/">Check out the tutorial here.</a></p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Stimulus Tutorial: Moving &amp; Animating Todos</title>
      <link>https://onrails.blog/2024/03/18/stimulus-moving-and-animating-todos</link>
      <guid isPermaLink="true">https://onrails.blog/2024/03/18/stimulus-moving-and-animating-todos</guid>
      <pubDate>Mon, 18 Mar 2024 15:56:29 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Drag and drop functions are a fun interaction, but they may not be the best interface in every situation. Buttons are a great affordance, and we can hook them up into our existing drag and drop code without any issue. Then we’ll look into animating the movement on the page so that it still feels interactive. </p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/RGsvL3z_Lz8","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/RGsvL3z_Lz8
</div><figcaption class="wp-element-caption">Demo of the movement when clicking the up and down buttons</figcaption></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Adding Buttons</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>On the right side of each Todo row, let’s add an up and down arrow button. These will get their own actions, <code>moveUp</code> and <code>moveDown</code> on the Stimulus controller that will be refactored into moving the Todo up and down, and sending the change to the server. The icons come from <a href="https://heroicons.com/">Heroicons</a>. </p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">  &lt;div class="flex flex-col h-full mr-2 divide-y w-fit"&gt;
    &lt;button class="w-6 h-6 border rounded-t-full bg-gray-50 hover:bg-gray-300 up"
      data-action="reorder#moveUp"&gt;
      &lt;svg xmlns="http://www.w3.org/2000/svg" 
      fill="none" 
      viewBox="0 0 24 24" 
      stroke-width="1.5" 
      stroke="currentColor" &gt;
        &lt;path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" /&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
    &lt;button 
      class="w-6 h-6 border rounded-b-full bg-gray-50 hover:bg-gray-300 down"
      data-action="reorder#moveDown"&gt;
      &lt;svg xmlns="http://www.w3.org/2000/svg" 
      fill="none" 
      viewBox="0 0 24 24" 
      stroke-width="1.5" 
      stroke="currentColor"&gt;
        &lt;path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" /&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
  &lt;/div&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Hiding Unnecessary Buttons</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>If you just refresh the HTML, you’ll see the top row has a button to move up, and the bottom row has a button to move down. This doesn’t make sense, and looks sloppy. We can use the power of CSS to hide those buttons automatically, and save us some work. Add these lines to your CSS file:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"css"} -->
<pre class="wp-block-syntaxhighlighter-code">div.todo:first-of-type &gt; div &gt; button.up {
  visibility: hidden;
}
div.todo:last-of-type &gt; div &gt; button.down {
  visibility: hidden;
}
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The outermost <code>div</code> in <code>todos\_todo.html.erb</code> gets a new class <code>todo</code> and each button has either the <code>up</code> or <code>down</code> class classes. We set up CSS selectors to hide the up button in the first Todo row, and the dow button on the last Todo row.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Updating the Stimulus controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The Stimulus controller gets refactored into a few new methods. The first is <code>moveItems</code> which moves the items on the page, and then performs the network update call.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">moveItems(item, target) {
    if (
      target.compareDocumentPosition(item) &amp; Node.DOCUMENT_POSITION_FOLLOWING
    ) {
      let result = target.insertAdjacentElement("beforebegin", item);
    } else if (
      target.compareDocumentPosition(item) &amp; Node.DOCUMENT_POSITION_PRECEDING
    ) {
      let result = target.insertAdjacentElement("afterend", item);
    }

    let formData = new FormData();
    formData.append("reorderable_target_id", target.dataset.reorderableId);

    fetch(item.dataset.reorderablePath, {
      body: formData,
      method: "PATCH",
      credentials: "include",
      dataType: "script",
      headers: {
        "X-CSRF-Token": getMetaValue("csrf-token"),
      },
      redirect: "manual",
    });
  }
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>moveDown</code> and <code>moveUp</code> functions now use this <code>moveItems</code> function. First the item checks to see if there is a sibling node in the requested direction, and if that sibling has a reorderable id.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">async moveUp(event) {
    let parent = getDataNode(event.target);
    if (
      parent.previousSibling.dataset != null &amp;&amp;
      parent.previousSibling.dataset.reorderableId != null
    ) {
      this.animateSwitch(parent.nextSibling, parent);
      await Promise.all(
        parent.getAnimations().map((animation) =&gt; animation.finished)
      );
      this.moveItems(parent, parent.previousSibling);
    }
    event.preventDefault();
  }

  async moveDown(event) {
    let parent = getDataNode(event.target);
    if (
      parent.nextSibling.dataset != null &amp;&amp;
      parent.nextSibling.dataset.reorderableId != null
    ) {
       this.animateSwitch(parent.nextSibling, parent);
      await Promise.all(
        parent.getAnimations().map((animation) =&gt; animation.finished)
      );
       this.moveItems(parent, parent.nextSibling);
    }

    event.preventDefault();
  }
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The best part is the <code>animateSwitch</code> method. It takes two rows, which it’s assumed are next to each other, and uses the <code>transform</code> property of an item to move the row either up or down in the vertical/Y direction. Once the animations are done, the <code>moveItems</code> will actually change the location of the rows in the DOM.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript"} -->
<pre class="wp-block-syntaxhighlighter-code">animateSwitch(from, to) {
    from.animate([{ transform: `translateY(-${from.clientHeight}px)` }], {
      duration: 300,
      easing: "ease-in-out",
    });

    to.animate([{ transform: `translateY(${to.clientHeight}px)` }], {
      duration: 300,
      easing: "ease-in-out",
    });
  } 
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Accessible and Interactive</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now, we have buttons that someone can use to move the Todos up and down, and we can use animation as an affordance that the change happened. And we use basic animations, rather than needing to import a whole UI framework. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You can see all the changes on <a href="https://github.com/OnRailsBlog/todo_app/tree/04ad04b0a24d07f0a246744d2034ade3cf0f8b26">Github</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":3} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>HOTWire Tutorial: Listening for changes over ActionCable</title>
      <link>https://onrails.blog/2024/03/14/62-hotwire-tutorial-listening-for-changes-over-actioncable</link>
      <guid isPermaLink="true">https://onrails.blog/2024/03/14/62-hotwire-tutorial-listening-for-changes-over-actioncable</guid>
      <pubDate>Thu, 14 Mar 2024 15:49:59 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:image {"id":976,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA1MywicHVyIjoiYmxvYl9pZCJ9fQ==--190b2a42ba70811717b56bb2e5296326f6f61cc2/HOTWire-Broadcasts-Sample-1024x569.png" alt="" class="wp-image-976"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Many of <a href="https://github.com/hotwired/turbo/releases/tag/v8.0.0">the changes in Turbo 8</a> are incredibly promising for improving the perception of speed and interactivity on our web apps. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>A lot of my Stimulus Tutorials need an update since they were first written, so follow along to update existing tutorials and rethink them with the newest tools available. Today we obsolete the tutorials <a href="https://onrails.blog/2018/12/14/grabbing-actioncable-with-stimulus-js/">Grabbing ActionCable with Stimulus.js</a> and <a href="https://onrails.blog/2019/01/23/subscribing-to-many-channels-in-actioncable/">Subscribing to many channels in ActionCable</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/HnJef2x4HVA","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/HnJef2x4HVA
</div></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Moving on from the Stimulus controllers </h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The initial tutorials required creating a Stimulus controller and calling the ActionCable code to setup the web socket connection. Now, Turbo automatically sets up those connections if the HTML includes the <code>turbo_stream_from</code> directive. In the Todo example, this means we could add the following to <code>todos\_todo.html</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= turbo_stream_from todo %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The generated HTML will look something like:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;turbo-cable-stream-source channel="Turbo::StreamsChannel" 
signed-stream-name="IloybGtPaTh2ZEc5a2J5MWhjSEF2Vkc5a2J5OHhNQSI=--d126352451e175c364445a3c1f22a1529c3243e4b8a686890f6783814af09e37" 
connected=""&gt;
&lt;/turbo-cable-stream-source&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Turbo has its own channel that will subscribe to all the names, and the channel name gets encrypted to protect from someone trying to guess the value.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">No extra Channels</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The previous examples also required setting up a ActionCable channel to stream events. Since we use the Turbo channel, this is no longer required. Instead, the model that needs to stream updates adds a helper method, and on creation, updates, and deletion, it gets events broadcasted over the Turbo channel. The change in Turbo 8 is that instead of needing to send new HTML over the web socket, the page refreshes and morphs the new elements to make it appear like everything changed.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The Todo item needs <code>broadcasts_refreshes</code> and all these updates are sent automatically to the front end for those Todos that are listened to.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Listening for new Todos</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>When creating a new Todo, there isn’t an existing channel listening for that Todo. Turbo handles this by broadcasting creations to a model specific channel, <code>"todos"</code> in this case. In <code>todos\index.html.erb</code>: adding the stream from directive will load in the Todos to the list:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">&lt;%= turbo_stream_from Todo.model_name.plural %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Touch to broadcast changes</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>When dragging and dropping the Todos, the <code>upsert</code> operation doesn’t automatically toggle a broadcast to make the front end refresh. By calling the touch method on the todo that was dragged, the front end is notified of the change, and the page refreshes with the updates.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby"} -->
<pre class="wp-block-syntaxhighlighter-code">@todo.touch</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Simplifying </h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The changes to Turbo over the past few years have allowed for better interactivity and better developer speed. These improvements are exceptional.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a href="https://github.com/OnRailsBlog/todo_app/tree/d344ffbd8420016b722508175317949baa27a663" target="_blank" rel="noreferrer noopener">You can see the code on Github.</a></p>
<!-- /wp:paragraph -->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Hotwire Tutorial: How Do I Drag and Drop Items in a List?</title>
      <link>https://onrails.blog/2024/03/08/hotwire-tutorial-how-do-i-drag-and-drop-items-in-a-list</link>
      <guid isPermaLink="true">https://onrails.blog/2024/03/08/hotwire-tutorial-how-do-i-drag-and-drop-items-in-a-list</guid>
      <pubDate>Fri, 08 Mar 2024 22:38:02 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>If you’ve been following <a href="https://github.com/hotwired/turbo/releases/tag/v8.0.0">the changes in Turbo 8</a>, it looks incredibly promising for improving the perception of speed and interactivity on our web apps. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>A lot of the Stimulus Tutorials could use an update since they were first written, so I thought it would be good to over existing tutorials and rethink them with the newest tools available. Join me as we rebuild the Stimulus Tutorial “<a title="How do I Drag and Drop Items in a list" href="https://onrails.blog/2018/03/09/stimulus-js-tutorial-how-do-i-drag-and-drop-items-in-a-list/">How do I Drag and Drop Items in a list</a></p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/riBrFASNaHs","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/riBrFASNaHs
</div><figcaption class="wp-element-caption">Client side tutorial</figcaption></figure>
<!-- /wp:embed -->

<!-- wp:embed {"url":"https://youtu.be/NuWWk6iWgwg","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/NuWWk6iWgwg
</div><figcaption class="wp-element-caption">Server side Tutorial</figcaption></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Setting Priority</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We’ll build off the previous example and add a priority order. This will be used sort the Todos.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ rails g migration AddPriorityToTodo priority:integer
$ rails db:migrate
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>If there are existing Todos in the database, go ahead and set the priority to the id of the existing Todo:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ rails c
* Todo.all.each do |todo|
*   todo.priority = todo.id
*   todo.save
&gt; end
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>In the <code>todos_controller.rb</code>, make the <code>index</code> method sort the Todos by this new priority field:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>def index
  @todos = Todo.all.order("priority")
end
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>And when creating a new Todo, we should set the priority to its ID after creating it by using a callback. This is a simplification since we only have a single Todo model in our application, and you may want to set priority based on other models that you design, like a Project or a List.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>class Todo &lt; ApplicationRecord
  after_create :set_priority

  private

  def set_priority
    self.priority = id
    save
  end
end
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>There are two parts we need to build next. A stimulus controller for the front end, and new rails controller to shuffle around the Todos and put them in the correct order by priority. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Reorder Stimulus Controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Like in the previous drag and drop tutorial, the controller needs to listen to a number of events to handle the dragging of an item. You can generate it with this command:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ rails g stimulus reorder
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The controller will use the <a href="https://stimulus.hotwired.dev/reference/css-classes">css classes</a> feature to apply styling as the list of Todos becomes active, as the dragged item moves around, and as a potential drop target is hovered over. This creates a very interactive effect, and helps show what the drag is doing. These go at the top of the controller:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  static classes = ["activeDropzone", "activeItem", "dropTarget"];
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The first event the controller listens for is <code>dragstart</code>. The <code>dragstart()</code> method will add the CSS classes to the element and the dragged item to provide more visual information as the drag happens. The drag event can transfer information, and the controller will set the id of the item that’s being dragged so when it can be referred to when it’s dropped.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  dragstart(event) {
    this.element.classList.add(...this.activeDropzoneClasses);
    const draggableItem = getDataNode(event.target);
    draggableItem.classList.add(...this.activeItemClasses);
    event.dataTransfer.setData(
      "application/drag-key",
      draggableItem.dataset.reorderableId
    );
    event.dataTransfer.effectAllowed = "move";
  } 
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The next event is <code>dragover</code>. It’s important to listen and respond to <code>true</code> so that we can eventually drop the Todo in the list.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  dragover(event) {
    event.preventDefault();
    return true;
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>At the bottom of the controller, there is a helper helper to get the parent div of the Todo that holds the reorderable id:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>function getDataNode(node) {
  return node.closest("[data-reorderable-id]");
} 
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This is used in the next few methods. <code>dragenter</code> and <code>dragleave</code> work together to change the appearance of the potential drop target. Both find the Todo node in the DOM. <code>dragenter()</code> adds the drop target styling with CSS classes, and then sets all the children pointer events to none so that the inner items, like the checkbox, the text, and the buttons don’t fire their own <code>dragenter</code> events. <code>dragleave()</code> removes the CSS classes, and unset’s the pointer events on the children elements.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  dragenter(event) {
    let parent = getDataNode(event.target);
    if (parent != null &amp;&amp; parent.dataset.reorderableId != null) {
      parent.classList.add(...this.dropTargetClasses);
      for (const child of parent.children) {
        child.classList.add("pointer-events-none");
      }
      event.preventDefault();
    }
  }

  dragleave(event) {
    let parent = getDataNode(event.target);
    if (parent != null &amp;&amp; parent.dataset.reorderableId != null) {
      parent.classList.remove(...this.dropTargetClasses);
      for (const child of parent.children) {
        child.classList.remove("pointer-events-none");
      }

      event.preventDefault();
    }
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>drop()</code> method uses a helper method to get the <a href="https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf">CSRF</a> token:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>function getMetaValue(name) {
  const element = document.head.querySelector(`meta[name="${name}"]`);
  return element.getAttribute("content");
}
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>drop</code> method gets the id the Todo that was dragged and the Todo that was dropped down. It doesn’t do anything special to validate the ordering, but it uses the fetch API to send an update to the rails controller we’ll add in the next section. It also repositions the dragged item in the DOM by comparing the document position and then inserting the item as an adjacent element. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  drop(event) {
    this.element.classList.remove(...this.activeDropzoneClasses);

    const dropTarget = getDataNode(event.target);
    dropTarget.classList.remove(...this.dropTargetClasses);
    for (const child of dropTarget.children) {
      child.classList.remove("pointer-events-none");
    }

    var data = event.dataTransfer.getData("application/drag-key");
    const draggedItem = this.element.querySelector(
      `[data-reorderable-id='${data}']`
    );

    if (draggedItem) {
      draggedItem.classList.remove(...this.activeItemClasses);

      if (
        dropTarget.compareDocumentPosition(draggedItem) &amp;
        Node.DOCUMENT_POSITION_FOLLOWING
      ) {
        let result = dropTarget.insertAdjacentElement(
          "beforebegin",
          draggedItem
        );
      } else if (
        dropTarget.compareDocumentPosition(draggedItem) &amp;
        Node.DOCUMENT_POSITION_PRECEDING
      ) {
        let result = dropTarget.insertAdjacentElement("afterend", draggedItem);
      }

      let formData = new FormData();
      formData.append(
        "reorderable_target_id",
        dropTarget.dataset.reorderableId
      );

      fetch(draggedItem.dataset.reorderablePath, {
        body: formData,
        method: "PATCH",
        credentials: "include",
        dataType: "script",
        headers: {
          "X-CSRF-Token": getMetaValue("csrf-token"),
        },
        redirect: "manual",
      });
    }
    event.preventDefault();
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Finally, the <code>dragend</code> event is used to remove CSS classes:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  dragend(event) {
    this.element.classList.remove(...this.activeDropzoneClasses);
  }
</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Priority Controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>First, put in a new directive to the <code>routes.rb</code> file to add a route to the <code>priorities_controller.rb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>  resources :todos do
    resource :priority, only: [:update]
  end
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>priorities_controller.rb</code> will only have one method, <code>update</code>. It will find the Todo that was dragged, and the Todo that was the drop target. Then, depending on the ordering, the priority on the all the Todos between those two items are swapped. The Upset command is used to update all the Todos at once. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>class PrioritiesController &lt; ApplicationController
  def update
    @todo = Todo.find_by_id params[:todo_id]
    @new_priority_todo = Todo.find_by_id params[:reorderable_target_id]

    if !@new_priority_todo.nil?
      old_priority = @todo.priority
      new_priority = @new_priority_todo.priority

      if old_priority &gt; new_priority
        todos = todos(new_priority..old_priority)
        (0..(todos.length - 2)).each do |i|
          first_todo = todos[i]
          second_todo = todos[i + 1]
          temp_priority = first_todo[:priority]
          first_todo[:priority] = second_todo[:priority]
          second_todo[:priority] = temp_priority
        end
        todos[todos.length - 1][:priority] = new_priority
        Todo.upsert_all(todos)
      elsif old_priority &lt; new_priority
        todos = todos(old_priority..new_priority)
        (todos.length - 1).downto(1).each do |i|
          first_todo = todos[i - 1]
          second_todo = todos[i]
          temp_priority = first_todo[:priority]
          second_todo[:priority] = first_todo[:priority]
          second_todo[:priority] = temp_priority
        end
        todos[0][:priority] = new_priority
        Todo.upsert_all(todos)
      end
    end
  end

  private

  def todos(range)
    Todo.where(priority: range).order(:priority)
      .pluck(:id, :priority, :updated_at, :created_at)
      .map { |id, priority, created_at, updated_at| {id: id, priority: priority, updated_at: updated_at, created_at: created_at} }
  end
end

</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Wiring up the Front End</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Finally, let’s add the annotations Stimulus needs in order to work. First, add the controller directives in <code>todos\index.html.erb</code>. Note the six drag actions that direct the drag and drop actions. The CSS classes are also setup with a number of Tailwind CSS classes. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;div id="todos" 
    class="min-w-full mt-1 overflow-hidden border rounded-lg" 
    data-controller="reorder"
    data-action="dragstart-&gt;reorder#dragstart dragover-&gt;reorder#dragover dragenter-&gt;reorder#dragenter dragleave-&gt;reorder#dragleave drop-&gt;reorder#drop dragend-&gt;reorder#dragend"
    data-reorder-active-dropzone-class="border-dashed bg-gray-50 border-slate-400"
    data-reorder-active-item-class="shadow"
    data-reorder-drop-target-class="shadow-inner shadow-gray-500"&gt;
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>todos/_todo.html.erb</code> needs three values, and then a visual drag icon is added. The first one is setting <code>draggable</code> to <code>true</code>, and set the <code>reorderable-id</code> and the <code>reorderable-path</code> that are used by Stimulus.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;div id="&lt;%= dom_id todo %&gt;" 
  class="flex items-center py-3 bg-white border-b gap-x-4" 
  data-reorderable-id="&lt;%= todo.id %&gt;" 
  data-reorderable-path="&lt;%= todo_priority_path(todo) %&gt;"
  draggable="true"&gt;
  &lt;div class="w-4 h-full -my-3 text-gray-900 rounded-sm hover:bg-gray-300 active:bg-gray-400 hover:cursor-grab active:cursor-grabbing" &gt;
    &lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-10"&gt;
      &lt;path d="M8 2a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM8 6.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM9.5 12.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0Z" /&gt;
    &lt;/svg&gt;
  &lt;/div&gt;

&lt;!--... remaining Todo HTML is untouched --&gt;
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now there is some very interactive Drag and Drop functionality on the Todo list.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/GEig8DlS5xs","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-16-9 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/GEig8DlS5xs
</div><figcaption class="wp-element-caption">Quick demo of Drag and Drop functionality</figcaption></figure>
<!-- /wp:embed -->

<!-- wp:paragraph -->
<p><a href="https://github.com/OnRailsBlog/todo_app/tree/04ad04b0a24d07f0a246744d2034ade3cf0f8b26" target="_blank" rel="noreferrer noopener">You can find the code on Github here.</a></p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Stimulus.js and HotWired Tutorial: Update Model with Checkbox using Turbo Morphing</title>
      <link>https://onrails.blog/2024/03/06/stimulusjs-tutorial-update-model-with-checkbox-using-turbo-morphing</link>
      <guid isPermaLink="true">https://onrails.blog/2024/03/06/stimulusjs-tutorial-update-model-with-checkbox-using-turbo-morphing</guid>
      <pubDate>Wed, 06 Mar 2024 14:28:00 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:image {"id":840,"sizeSlug":"large","linkDestination":"media"} -->
<figure class="wp-block-image size-large"><a href="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA1OSwicHVyIjoiYmxvYl9pZCJ9fQ==--c8326ebfd82238e92792a554ccd62f1f5ec6d2fd/Stimulus-JS-Turbo-Morph-Remote-Checkbox-Tutorial.png"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTA1OCwicHVyIjoiYmxvYl9pZCJ9fQ==--fec6d21de75f394482b3858cc07881f4ba0a25f2/Stimulus-JS-Turbo-Morph-Remote-Checkbox-Tutorial-1024x700.png" alt="" class="wp-image-840"></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>If you’ve been following <a href="https://github.com/hotwired/turbo/releases/tag/v8.0.0">the changes in Turbo 8</a>, it looks incredibly promising for improving the perception of speed and interactivity on our web apps. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>A lot of the Stimulus Tutorials could use an update since they were first written, so I thought it would be good to over existing tutorials and rethink them with the newest tools available. Join me as we rebuild the first Stimulus Tutorial, and <a href="https://onrails.blog/2020/11/09/updated-tutorial-how-do-i-update-my-model-from-a-checkbox/">it’s updated version</a>, “<a href="https://onrails.blog/2018/03/13/stimulus-js-tutorial-how-do-i-update-my-model-from-a-checkbox/">How do I Remotely Update My Model from a checkbox</a>”.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/dB7vFHX7aqY","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/dB7vFHX7aqY
</div></figure>
<!-- /wp:embed -->

<!-- wp:paragraph -->
<p>First, start by creating a fresh Rails app. I’m using Rails 7.1 and Ruby 3.3.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ rails new todo_app -c tailwind</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We need to update the <code>application.html.erb</code> file to use the Turbo 8 Morphing by adding these lines inside the <code>&lt;head&gt;</code> tag.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;% turbo_refreshes_with method: :morph, scroll: :preserve %&gt;
&lt;%= yield :head %&gt;</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Let’s scaffold out a Todo Item that has a name and its completed state.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>$ rails g scaffold Todo name:string completed:boolean
$ rails db:migrate </code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The default views for the Todo could use some tweaking to make it look more like actionable item, so update the <code>todos/_todo.html.erb</code> view to this:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;div id="&lt;%= dom_id todo %&gt;" class="flex items-center py-3 border-b gap-x-4"&gt;
  &lt;input type="checkbox" &lt;%= "checked" if todo.completed %&gt;&gt;
  &lt;span class="flex-1 "&gt;&lt;%= todo.name %&gt;&lt;/span&gt;
  &lt;% if action_name != "show" %&gt;
    &lt;%= link_to "Show this todo", todo, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %&gt;
    &lt;%= link_to "Edit this todo", edit_todo_path(todo), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %&gt;
  &lt;% end %&gt;
&lt;/div&gt;</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We need to tweak the checkbox so that changing the value will be updated on the server. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Change the checkbox input to a small form that will trigger a form submission when it’s toggled.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>&lt;div id="&lt;%= dom_id todo %&gt;" class="flex items-center py-3 border-b gap-x-4"&gt;
  &lt;%= form_with model: todo, data: { controller: "form" } do |form| %&gt;
    &lt;%= form.label :completed, for: dom_id(todo, :completed) do %&gt;
      &lt;%= form.check_box :completed, id: dom_id(todo, :completed), data: { action: "form#submit" } %&gt;
    &lt;% end %&gt;
  &lt;% end %&gt;
  &lt;span class="flex-1 "&gt;&lt;%= todo.name %&gt;&lt;/span&gt;
  &lt;% if action_name != "show" %&gt;
    &lt;%= link_to "Show this todo", todo, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %&gt;
    &lt;%= link_to "Edit this todo", edit_todo_path(todo), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %&gt;
  &lt;% end %&gt;
&lt;/div&gt;</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Add a Stimulus controller, <code>form_controller.js</code> that will submit the form when the checkbox is changed:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  submit() {
    this.element.requestSubmit();
  }
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Change the <code>todos_controller.rb</code> update method to redirect back to the Todos list:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code> def update
    respond_to do |format|
      if @todo.update(todo_params)
        format.html { redirect_to todos_url, notice: "Todo was successfully updated." }
        format.json { render :show, status: :ok, location: @todo }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @todo.errors, status: :unprocessable_entity }
      end
    end
  end
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now, we get the same benefits of a remotely filled in form, while using leaning on Turbo’s morphing ability.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":3} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Displaying Progress in a Long Running Background Job using HOTWire</title>
      <link>https://onrails.blog/2022/11/07/displaying-progress-in-a-long-running-background-job-hotwire</link>
      <guid isPermaLink="true">https://onrails.blog/2022/11/07/displaying-progress-in-a-long-running-background-job-hotwire</guid>
      <pubDate>Mon, 07 Nov 2022 14:31:42 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Fast UIs need to feel like a lot is happening, even if that’s in the background and out of site of your user. On a mobile app, usually that means moving network calls outside the “main thread” and into a background thread that calls back. On websites, it means making every request back to the server as short as possible, and then waiting for some update back from the server. This could be accomplished by polling the server, but with ActionCable and Turbo Streams, you can let the server do whatever background work is needed and have it push those updates to you. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>One example is processing CSV files from your users. Sometimes APIs are great for getting data into your app, and you can’t beat comma separated values for updating records. The general idea is updating several records based off rows in a spreadsheet. This can be broken down into two steps: upload the file, and process the file. </p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":821,"sizeSlug":"full","linkDestination":"media"} -->
<figure class="wp-block-image size-full"><a href="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAzMywicHVyIjoiYmxvYl9pZCJ9fQ==--02cd8f9104ade54c54bf9b2c1fc92ae7e678c210/04.2-HOTWire-Background-Job-Loading-Animation.gif"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAzMywicHVyIjoiYmxvYl9pZCJ9fQ==--02cd8f9104ade54c54bf9b2c1fc92ae7e678c210/04.2-HOTWire-Background-Job-Loading-Animation.gif" alt="" class="wp-image-821"></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Uploading the file can be done with JavaScript and a loading bar, so we know when the file is done uploading, and how much more progress there is. Processing the file happens on the backend, and that’s where getting visibility into the process can be tough. Do we create a database record that we use to keep track of the progress? Does that record get purged after the upload and processing are complete? Those are business problems that can be different depending on the situation. But given that the server that renders a webpage might be different from the server that processes the file, putting the file into a cloud bucket makes the most sense, at least until we’re done with the processing. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>On the server side, using web sockets to communicate progress back to the front end can provide a sense of progress, and relief that something is actually happening, whether it’s 10 or 10,000 rows in the spreadsheet. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You can probably come up with other processes that need to display background progress, such sending out batches of notifications, but let’s work through processing books like <a href="https://onrails.blog/2022/10/31/55-adding-loading-screen-with-turbo/">in our previous Book example.</a></p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Uploading a CSV file</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We can use ActiveStorage to help get the file into our system, and to help keep track of progress throughout the upload and processing. Each Blob in ActiveStorage has a database record, and can be purged once the whole process is complete. ActiveStorage also has a mechanism to put the file directly into the cloud or file storage we’re using, and it emits off progress events that we can use to show a loading indicator on the page. Once the file is uploaded, then we send the key back to the server and start processing the file. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Start with a clean app:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails new BookImports -c tailwind
$ cd BookImports 
$ rails g scaffold Book title:string author:string publisher:string category:string isbn:string dewey_decimal_number:string binding:integer
$ bin/rails active_storage:install 
$ rails db:migrate</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>We’re going to make binding an enum since there are only a few options, so change <code>models/book.rb</code> to this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">class Book &lt; ApplicationRecord
  enum binding: [:hardcover, :paperback]
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>To test our system with plenty of books, We will use the Faker gem to create about 100 fake books in a CSV file that we can upload.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In our Gemfile:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">gem 'faker'</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then run</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ bundle install</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then we’ll create the books in our <code>db/seeds.rb</code> file.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">require 'csv'

path = 'public/books.csv'

CSV.open(path, 'w', headers: ['title', 'author', 'publisher', 'category', 'isbn', 'dewey_decimal_number', 'binding'], write_headers: true) do |csv|
  100.times do |i|
    csv &lt;&lt; [Faker::Book.title, Faker::Book.author, Faker::Book.publisher, Faker::Book.genre, "#{Faker::Number.number(digits: 3)}-#{Faker::Number.number(digits: 1)}-#{Faker::Number.number(digits: 2)}-#{Faker::Number.number(digits: 6)}-#{Faker::Number.number(digits: 1)}", "#{Faker::Number.number(digits: 3)}.#{Faker::Number.number(digits: 3)}", Faker::Number.between(from: 0, to: 1)]
  end
end </pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Working on the import pages, generate the controller:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails g controller Import show create</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Change the route for <code>create</code> into a post in <code>routes.rb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">post 'import/create'</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>In the form, we’re going to set it as multipart, so we can upload a file, but first, let’s create the Stimulus controller that will show the upload progress.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails g stimulus uploads</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Add the ActiveStorage direct upload library with your current JavaScript management tool, for example with importmap:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ ./bin/importmap pin @rails/activestorage</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p><a href="https://edgeguides.rubyonrails.org/active_storage_overview.html#usage">Add ActiveStorage to your <code>javascript/application.js</code>:</a></p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">import * as ActiveStorage from "@rails/activestorage";
ActiveStorage.start();</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>uploads_controller.js</code> is going to listen for two events: <code>direct-upload:initialize@window</code> which will trigger showing the progress indicator, and <code>direct-upload:progress@window</code>, which will give us the current upload progress. The controller has a wrapper around the progress indicator and the progress indicator itself, which are updated by these events.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="uploads"
export default class extends Controller {
  static targets = ["progress", "progressWrapper"];

  initializeDirectUpload() {
    this.progressWrapperTarget.classList.remove("hidden");
  }

  progressDirectUpload(event) {
    const { id, progress } = event.detail;
    this.progressTarget.style.width = `${progress}%`;
  }
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The form, <code>views/import/show.html.erb</code>, is as follows:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div class="max-w-3xl mx-auto"&gt;
  &lt;h1 class="font-bold text-4xl"&gt;Import Books&lt;/h1&gt;
  &lt;%= form_with url: import_create_path, multipart: true do %&gt;
    &lt;div class="divide-y divide-gray-200 space-y-5"&gt;
      &lt;div class="mt-5 space-y-5"&gt;
        &lt;div class="grid grid-cols-3 gap-4 items-start border-t border-gray-200 pt-5"&gt;
          &lt;label  class="block text-sm font-medium text-gray-700 mt-px pt-2"&gt;Data File (CSV):&lt;/label&gt;
          &lt;div class="mt-0 col-span-2" 
            data-controller='uploads' 
            data-action='direct-upload:initialize@window-&gt;uploads#initializeDirectUpload  direct-upload:progress@window-&gt;uploads#progressDirectUpload' &gt;
            &lt;div class="max-w-lg flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"&gt;
              &lt;div class="space-y-1 text-center"&gt;
                &lt;div class="w-full flex text-sm text-gray-600"&gt;
                  &lt;label for="file" class="relative cursor-pointer bg-white rounded-md font-medium text-blue-700 hover:text-blue-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500"&gt;
                    &lt;%= file_field_tag :file, class: 'input', direct_upload: true %&gt;
                  &lt;/label&gt;
                &lt;/div&gt;
                &lt;div class="w-full pt-1 hidden" 
                     data-uploads-target="progressWrapper"&gt;
                  &lt;span&gt;Uploading file&lt;/span&gt;
                  &lt;div class="overflow-hidden w-60 h-4 text-xs flex rounded bg-blue-200"&gt;
                    &lt;div data-uploads-target="progress"
                         style="width: 0%"
                         class=" shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-blue-800" &gt;&lt;/div&gt;
                  &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="pt-5"&gt;
          &lt;div class="flex justify-end"&gt;
            &lt;%= submit_tag "Upload File", class: "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" %&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;% end %&gt;
&lt;/div&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>At this point, when you upload the file, you get a nice progress indicator, but the controller doesn’t respond appropriately, so nothing happens on the page. We need to handle the uploaded file. Let’s start by creating a background job that will process the CSV.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ bin/rails generate job ImportBooks</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>create</code> action on the import controller will queue the import job by passing in the blob, and then pass back the HTML that will display the progress indicator, and create the ActionCable channel that will listen for progress updates. Let’s wrap the form from the <code>show.html.erb</code> page with a <code>turbo_frame_tag</code> so that the response can replace that part of the response.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div&gt;
  &lt;h1 class="font-bold text-4xl"&gt;Import Books&lt;/h1&gt;
  &lt;%= turbo_frame_tag "import" do %&gt;
    &lt;%= form_with url: import_create_path, multipart: true do %&gt;
      &lt;!-- the rest of the form is ommitted for brevity --&gt;
    &lt;% end %&gt;
  &lt;% end %&gt;
&lt;/div&gt; </pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>create</code> template should be renamed to <code>create.turbo_stream.erb</code> and will have:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= turbo_stream.replace "import" do %&gt;
  &lt;%= turbo_stream_from @blob.key %&gt;
  &lt;div class="divide-y divide-gray-200 space-y-5"&gt;
    &lt;div class="mt-5 space-y-5"&gt;
      &lt;div class="grid grid-cols-3 gap-4 items-start border-t border-gray-200 pt-5"&gt;
        &lt;label  class="block text-sm font-medium text-gray-700 mt-px pt-2"&gt;Data File (CSV):&lt;/label&gt;
        &lt;div class="mt-0 col-span-2" 
            data-controller='uploads' 
            data-action='direct-upload:initialize@window-&gt;uploads#initializeDirectUpload  direct-upload:progress@window-&gt;uploads#progressDirectUpload' &gt;
          &lt;div class="max-w-lg flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"&gt;
            &lt;div class="space-y-1 text-center"&gt;
              &lt;div class="w-full flex text-sm text-gray-600"&gt;
                &lt;label for="file" class="relative cursor-pointer bg-white rounded-md font-medium text-blue-700 hover:text-blue-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500"&gt;
                  &lt;%= @blob.filename %&gt;
                &lt;/label&gt;
              &lt;/div&gt;
              &lt;div class="w-full pt-1"&gt;
                &lt;span&gt;Processing file&lt;/span&gt;
                &lt;div class="overflow-hidden w-60 h-4 text-xs flex rounded bg-blue-200"&gt;
                  &lt;%= render partial: "blob_progress", locals: { blob: @blob, progress: 0 } %&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class="pt-5"&gt;
        &lt;div class="flex justify-end"&gt;
          &lt;%= link_to "View Results", books_path, class: "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" %&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>We’re removing the form, but keeping the layout similar to what’s replaced. It uses the <code>turbo_stream_from</code> tag to listen on the blob’s key. This gets around the fact that <code>ActiveStorage::Blob</code> has a different result for the identify method that the TurboStream class uses to create the channel. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Finally, the ImportBooksJob is queued in the <code>create</code> action:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">class ImportController &lt; ApplicationController
  def show
  end

  def create
    @blob = ActiveStorage::Blob.find_signed params[:file]
    ImportBooksJob.perform_later @blob
    respond_to do |format|
      format.turbo_stream
    end
  end
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Processing a file</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>In this sample, we’re using a CSV and the processing will be in the background job <code>ImportBooks</code>. The job will download the file from file storage, use Ruby’s CSV library to read each row, and then update or insert records depending on what the file contains. One technique I’ve used is to farm out each record into its job, so that if any errors occur, they don’t stop the entire import, but We’ll keep this simple for now.  The progress will be reported back to the front end through the TurboStream connection.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Let’s create a little template that we’ll use to render the progress, <code>app/views/application/_blob_progress.html.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div id="&lt;%= dom_id blob %&gt;" class="rounded animate-pulse shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-blue-800" style="width: &lt;%= progress %&gt;%" &gt;&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This takes in the blob and the amount of progress as local variables.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The <code>ImportBooksJob</code> is passed the blob, downloads the file, and parses the CSV. After each iteration, it sends the progress to the front end, and we get a nice loading bar on the page.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">require 'csv'

class ImportBooksJob &lt; ApplicationJob
  queue_as :default

  def perform(blob)
    blob.open do |file|
       csv = CSV.read(file, headers: true)
       row_count = CSV.read(file, headers: true).size
       csv.each_with_index do |row, progress|
        Turbo::StreamsChannel.broadcast_replace_to blob.key, 
                                                  target: blob, partial: "blob_progress", 
                                                  locals: { blob: blob, progress: ((progress.to_f / row_count) * 100) }
       end
    end
  end
end
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>Turbo::StreamsChannel</code> can broadcast to the front end. The first parameter, <code>blob.key</code> which matches what we are listening for on the page.  The target matches the DOM id on the page, which is the progress indicator, and the partial and locals named parameters are considered the rendering block. This all gets wrapped into a template, and replaced on the front page.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Passive yet Interactive</h2>
<!-- /wp:heading -->

<!-- wp:video {"id":823} -->
<figure class="wp-block-video"><video controls="" src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAzMSwicHVyIjoiYmxvYl9pZCJ9fQ==--819c46095e876fdd1af2c2819972b4bb4a66c317/04-HOTWire-Background-Job-Loading-1.mp4"></video></figure>
<!-- /wp:video -->

<!-- wp:paragraph -->
<p>The user wanted to load up a lot of records into the app. We provided a way to use a CSV to upload all the records, so they wouldn’t have to add each one manually. They get a progress bar for the upload, which is delightful if we have a big file, but it still feels luxurious for our website to tell us what’s going on. Once the files been uploaded, a new progress indicator shows how the record import is going. The whole action by the user was a few clicks, but they feel in control because they know what’s going on the whole time. This is the kind of interactivity uses crave in a web app, and a lot of it comes for free in Rails.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Adding Loading Screen with Turbo</title>
      <link>https://onrails.blog/2022/10/31/55-adding-loading-screen-with-turbo</link>
      <guid isPermaLink="true">https://onrails.blog/2022/10/31/55-adding-loading-screen-with-turbo</guid>
      <pubDate>Mon, 31 Oct 2022 13:57:05 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>One great aspect of Turbo frames is lazy loading. You can use this behavior to quickly load in the shell of a UI, and then lazy load all the data. Adding an animating loading status will help give the feeling of immediacy and “a lot of work is happening in the background” without the frustration that the page is stuck. We don’t even need any extra Javascript, since we can animate the page with CSS.</p>
<!-- /wp:paragraph -->

<!-- wp:embed {"url":"https://youtu.be/lXxFzPU-_uE","type":"video","providerNameSlug":"youtube","responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} -->
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
https://youtu.be/lXxFzPU-_uE
</div><figcaption class="wp-element-caption">Full tutorial</figcaption></figure>
<!-- /wp:embed -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Loading many books</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This tutorial will start from a previous example, <a href="https://onrails.blog/2018/09/25/modeling-more-complex-datasets-in-rails/">Modeling More Complex Datasets</a>, which used a Book model. We’ll start from scratch with a fresh Rails app and use TailwindCSS for the style.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails new BookData -c tailwind
$ cd BookData 
$ rails g model Book title:string author:string publisher:string category:string isbn:string dewey_decimal_number:string binding:integer
$ rails db:migrate</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>We’re going to make binding an enum since there are only a few options, so change <code>models/book.rb</code> to this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">class Book &lt; ApplicationRecord
  enum binding: [:hardcover, :paperback]
end
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>To test our system with plenty of books, We will use the Faker gem to create about 2000 fake books.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In our Gemfile:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">gem 'faker'</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then run</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ bundle install</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then we’ll create the books in our <code>db/seeds.rb</code> file.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">2_000.times do |i|
  Book.create!( title: Faker::Book.title, 
	author: Faker::Book.author, 
	publisher: Faker::Book.publisher,
	category: Faker::Book.genre,
	isbn: "#{Faker::Number.number(digits: 3)}-#{Faker::Number.number(digits: 1)}-#{Faker::Number.number(digits: 2)}-#{Faker::Number.number(digits: 6)}-#{Faker::Number.number(digits: 1)}",
	dewey_decimal_number: "#{Faker::Number.number(digits: 3)}.#{Faker::Number.number(digits: 3)}",
	binding: Faker::Number.between(from: 0, to: 1))
  print '.' if i % 100 == 0
end
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now we have a lot of books records that we can use.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Let’s build an index page so that we can look at our books.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails g controller books index</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This will update&nbsp;<code>routes.rb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">Rails.application.routes.draw do
  get 'books/index'
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And we need to update <code>app/controllers/books_controller.rb</code>&nbsp;file with an index action, and we’ll start by loading all the books.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">class BooksController &lt; ApplicationController
  def index
    @books = Book.all
  end
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>And finally, a view at&nbsp;<code>app/views/books/index.html.erb</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div&gt;
  &lt;h1 class="font-bold text-4xl"&gt;All Books&lt;/h1&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Title&lt;/th&gt;
        &lt;th&gt;Author&lt;/th&gt;
        &lt;th&gt;Publisher&lt;/th&gt;
        &lt;th&gt;Category&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;% @books.each do |book| %&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;%= book.title %&gt;&lt;/td&gt;
          &lt;td&gt;&lt;%= book.author %&gt;&lt;/td&gt;
          &lt;td&gt;&lt;%= book.publisher %&gt;&lt;/td&gt;
          &lt;td&gt;&lt;%= book.category %&gt;&lt;/td&gt;
        &lt;/tr&gt;
      &lt;% end %&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Run&nbsp;<code>./bin/dev</code>&nbsp;and visit it in the browser.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":770,"linkDestination":"none","align":"center"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAzMCwicHVyIjoiYmxvYl9pZCJ9fQ==--5631c1d606b05617b20d21e30b4595119618023e/DraggedImage.png" alt="" class="wp-image-770"></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Lazy Loading </h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The URL path <code>/books/index</code> is a little awkward. It would be great if it would load when the site loads up. Let’s add a root controller that will load the books page:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails g controller root index</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Change the <code>routes.rb</code> file to load at the root page:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">Rails.application.routes.draw do
  get 'books/index'
  root "root#index"
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Add a <code>turbo-frame</code> element to the <code>root/index.html.erb</code> page:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div&gt;
  &lt;h1 class="font-bold text-4xl"&gt;Root Page&lt;/h1&gt;
  &lt;%= turbo_frame_tag "books", src: books_index_path %&gt;
&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>If you don’t change the book’s index page, this will load all the books in, and overwrite the URL. We want to hide the <code>/books/index</code> path, so let’s wrap the table in <code>views/books/index.html.erb</code> in a <code>turbo_frame_tag</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div&gt;
  &lt;h1 class="font-bold text-4xl"&gt;All Books&lt;/h1&gt;
  &lt;%= turbo_frame_tag "books" do %&gt;
    &lt;table&gt;
      &lt;thead&gt;
        &lt;tr&gt;
          &lt;th&gt;Title&lt;/th&gt;
          &lt;th&gt;Author&lt;/th&gt;
          &lt;th&gt;Publisher&lt;/th&gt;
          &lt;th&gt;Category&lt;/th&gt;
        &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
        &lt;% @books.each do |book| %&gt;
          &lt;tr&gt;
            &lt;td&gt;&lt;%= book.title %&gt;&lt;/td&gt;
            &lt;td&gt;&lt;%= book.author %&gt;&lt;/td&gt;
            &lt;td&gt;&lt;%= book.publisher %&gt;&lt;/td&gt;
            &lt;td&gt;&lt;%= book.category %&gt;&lt;/td&gt;
          &lt;/tr&gt;
        &lt;% end %&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;% end %&gt;
&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now, when you load the root page, there is a skeleton that loads in the books table.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":769,"linkDestination":"none","align":"center"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyNywicHVyIjoiYmxvYl9pZCJ9fQ==--5ac2c0d8e2a7d15084c99bb0921efb88040c0224/03.1-HOTWire-Lazy-Loading-Tables.gif" alt="" class="wp-image-769"></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Animate the loading</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Loading all these books could take a while. This could stand in for a different page with more complicated database calls that could take a few seconds. Let’s put in a small skeleton, animate a shimmering effect, and give the appearance that the page is working in the background. Look at the revised <code>/views/root/index.html.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div&gt;
  &lt;h1 class="font-bold text-4xl"&gt;Root Page&lt;/h1&gt;
  &lt;%= turbo_frame_tag "books", src: books_index_path do %&gt;
    &lt;table class="animate-pulse"&gt;
      &lt;thead&gt;
        &lt;tr&gt;
          &lt;th&gt;Title&lt;/th&gt;
          &lt;th&gt;Author&lt;/th&gt;
          &lt;th&gt;Publisher&lt;/th&gt;
          &lt;th&gt;Category&lt;/th&gt;
        &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
        &lt;% (1..10).each do %&gt;
          &lt;tr&gt;
            &lt;td&gt;&lt;div class="bg-slate-200 h-8 rounded w-64"&gt;&lt;/div&gt;&lt;/td&gt;
            &lt;td&gt;&lt;div class="bg-slate-200 h-8 rounded w-64"&gt;&lt;/div&gt;&lt;/td&gt;
            &lt;td&gt;&lt;div class="bg-slate-200 h-8 rounded w-64"&gt;&lt;/div&gt;&lt;/td&gt;
            &lt;td&gt;&lt;div class="bg-slate-200 h-8 rounded w-64"&gt;&lt;/div&gt;&lt;/td&gt;
          &lt;/tr&gt;
        &lt;% end %&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;% end %&gt;
&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The turbo-frame now has a block that puts in a skeleton table. It uses the tailwind animate-pulse class, which is a pulsing CSS animation. Instead of iterating through book records, it generates 10 rows. Each <code>td</code> cell has a rounded div that acts a visual placeholder. Loading the page shows a pleasant placeholder while all the books come in over the wire.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":768,"linkDestination":"none","align":"center"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--3fe80db07935be3ffd67668654a73cd8e1f3f0eb/03.2-HOTWire-Lazy-Loading-Tables-with-animation.gif" alt="" class="wp-image-768"></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Free Interactivity</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This shows how we can use a lazy loading page to add interactivity to our page with little extra work, and no extra JavaScript. You could imagine having to see up a Stimulus controller that loaded data in when it connected. Turbo allows us to skip even that step, and just use basic HTML and an extra Rails controller. You could even build a dashboard that loaded data from several sources.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Interactive HOTWire Notifications with Turbo Streams</title>
      <link>https://onrails.blog/2022/09/19/hotwire-notifications</link>
      <guid isPermaLink="true">https://onrails.blog/2022/09/19/hotwire-notifications</guid>
      <pubDate>Mon, 19 Sep 2022 10:00:00 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Rails’ use of <code>flash</code> messages is a great way to provide context for customer actions. If they delete an object, flashing a notice that the delete action succeeded, or perhaps failed, gives them more context to make the next decision. With HOTWire’s asynchronous nature, you don’t get those notifications in the same way, especially if you’re just replacing a part of a page.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Refactoring Notifications</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Let’s refactor the flash messages into the upper-left corner of the app, where they’ll float. Using a TailwindUI design, the key component is something with an <code>id</code> where Turbo can append the notification. I’m using a <code>div</code> with the <code>id="notifications"</code>. I refactored the HTML for an alert or a notice into a partial, which I put in the <code>app/views/layouts</code> folder as <code>_alert.html.erb</code> and <code>_notice.html.erb</code>. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Displaying Alerts from TurboStreams</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>In any <code>*.turbo_stream.erb</code> view, add the following to check if there is an alert or notification:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;% if notice %&gt;
  &lt;%= turbo_stream.append 'notifications' do %&gt;
    &lt;%= render 'layouts/notice' %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;

&lt;% if alert %&gt;
  &lt;%= turbo_stream.append 'notifications' do %&gt;
    &lt;%= render 'layouts/alert' %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This will append the notification to the notifications' element, and display the notification.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Building the notifications</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>You can start with a new project, and scaffold a Post model:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ rails new notification_test -c tailwind
$ rails g scaffold Post title:string body:text
$ rails db:migrate
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>We’re going to add the HTML code to display our notifications. In <code>app/views/layout/application.html.erb</code>, add the notifications “home”, which won’t show anything, but Turbo will append notification onto it.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;body&gt;
  &lt;main class="container mx-auto mt-28 px-5 flex"&gt;
    &lt;div aria-live="assertive" class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-50"&gt;
      &lt;div id="notifications" class="w-full flex flex-col items-center space-y-4 sm:items-end"&gt;
        &lt;% if notice %&gt;
          &lt;%= render 'layouts/notice' %&gt;
        &lt;% end %&gt;
        &lt;% if alert %&gt;
          &lt;%= render 'layouts/alert' %&gt;
        &lt;% end %&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;%= yield %&gt;
  &lt;/main&gt;
&lt;/body&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>In the <code>app/views/layouts</code> folder, add two almost identical partials that will display the alert or notice. Here is <code>app/views/layout/_alert.html.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div data-controller="alert"
  data-transition-enter="transform ease-out duration-300 transition"
  data-transition-enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
  data-transition-enter-end="translate-y-0 opacity-100 sm:translate-x-0"
  data-transition-leave="transition ease-in duration-100"
  data-transition-leave-start="opacity-100"
  data-transition-leave-end="opacity-0"
  class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden hidden"&gt;
  &lt;div class="p-4"&gt;
    &lt;div class="flex items-start"&gt;
      &lt;div class="flex-shrink-0"&gt;
        &lt;svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"&gt;
          &lt;path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /&gt;
        &lt;/svg&gt;
      &lt;/div&gt;
      &lt;div class="ml-3 w-0 flex-1 pt-0.5"&gt;
        &lt;p class="text-sm font-medium text-gray-900"&gt;Alert!&lt;/p&gt;
        &lt;p class="mt-1 text-sm text-gray-500"&gt;&lt;%= alert %&gt;&lt;/p&gt;
		&lt;%# Need to clear flash[:alert] once displayed %&gt;
        &lt;% flash[:alert] = nil %&gt;
      &lt;/div&gt;
      &lt;div class="ml-4 flex-shrink-0 flex"&gt;
        &lt;button type="button" data-action="alert#close"  class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"&gt;
          &lt;span class="sr-only"&gt;Close&lt;/span&gt;
          &lt;!-- Heroicon name: solid/x --&gt;
          &lt;svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"&gt;
            &lt;path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /&gt;
          &lt;/svg&gt;
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Here is the notice in <code>app/views/layout/_notice.html.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div data-controller="alert"
  data-transition-enter="transform ease-out duration-300 transition"
  data-transition-enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
  data-transition-enter-end="translate-y-0 opacity-100 sm:translate-x-0"
  data-transition-leave="transition ease-in duration-100"
  data-transition-leave-start="opacity-100"
  data-transition-leave-end="opacity-0"
  class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden hidden"&gt;
  &lt;div class="p-4"&gt;
    &lt;div class="flex items-start"&gt;
      &lt;div class="flex-shrink-0"&gt;
        &lt;!-- Heroicon name: outline/check-circle --&gt;
        &lt;svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;
          &lt;path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /&gt;
        &lt;/svg&gt;
      &lt;/div&gt;
      &lt;div class="ml-3 w-0 flex-1 pt-0.5"&gt;
        &lt;p class="text-sm font-medium text-gray-900"&gt;Notice!&lt;/p&gt;
        &lt;p class="mt-1 text-sm text-gray-500"&gt;&lt;%= notice %&gt;&lt;/p&gt;
      	&lt;%# Need to clear flash[:notice] once displayed %&gt;
        &lt;% flash[:notice] = nil %&gt;
	  &lt;/div&gt;
      &lt;div class="ml-4 flex-shrink-0 flex"&gt;
        &lt;button type="button" data-action="alert#close"  class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"&gt;
          &lt;span class="sr-only"&gt;Close&lt;/span&gt;
          &lt;!-- Heroicon name: solid/x --&gt;
          &lt;svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"&gt;
            &lt;path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /&gt;
          &lt;/svg&gt;
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now, when you perform an action, like creating a Post, you’ll see the notification floating above.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":751,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--607f5b0439e5ed356691e826ff8f0356f25c5bab/DraggedImage-2.png" alt="" class="wp-image-751"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Hopefully, you noticed the Stimulus alert controller that will make this floating notification disappear after appearing, or allow the user to click the X to remove the notification. This uses the <a href="https://github.com/mmccall10/el-transition"><code>el-transition</code></a> library to manage the animations. Add it with your current JavaScript management tool, for example with importmap:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ ./bin/importmap pin el-transition</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>or yarn:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ yarn add el-transition</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Add the Stimulus controller, <code>alert_controller.js</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">import { Controller } from "@hotwired/stimulus";
import { enter, leave } from "el-transition";

let TIMEOUT_MILLISECONDS = 2000;

export default class extends Controller {
  connect() {
    enter(this.element).then(() =&gt; {
      setTimeout(() =&gt; {
        this.close();
      }, TIMEOUT_MILLISECONDS);
    });
  }

  close() {
    leave(this.element).then(() =&gt; {
      this.element.remove();
    });
  }
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now we have a nice floating notification layout, with a Stimulus controller providing the entering and leaving animations, and a timeout that removes the notification after a few seconds. Let’s add the Turbo Stream magic.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">TurboFrames and TurboStreams</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>First, on the <code>posts/index.html.erb</code> page, let’s wrap the “New Post” button in a <code>turbo_frame_tag</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= turbo_frame_tag 'new_post' do %&gt;
  &lt;%= link_to 'New post', new_post_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %&gt;
&lt;% end %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>In the <code>posts/new.html.erb</code>, wrap the key part of the form in the same turbo frame. This will be styled to be a modal that appears in the middle of the page:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= turbo_frame_tag 'new_post' do %&gt;
  &lt;div class="fixed inset-0 z-10 overflow-y-auto"&gt;
    &lt;div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"&gt;
      &lt;div class="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"&gt;
        &lt;h1 class="font-bold text-4xl"&gt;New post&lt;/h1&gt;
        &lt;%= render "form", post: @post %&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;% end %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Turbo is going to handle the replacement, so the page will load the new form in the middle of the page.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":750,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyNCwicHVyIjoiYmxvYl9pZCJ9fQ==--696ceca2bb83a3aa62a534b7a69d8a2e6ce800d5/DraggedImage-1.png" alt="" class="wp-image-750"></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>When the “Create Post” button is clicked, a request goes to Posts controller on the <code>create</code> action. Add a new format response to the create action, complete with the flash notices:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">  # POST /posts or /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.turbo_stream { flash[:notice] = 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.turbo_stream { flash[:alert] = 'Post was not created.' }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Let’s add the <code>posts/create.turbo_stream.erb</code> template that will append the new post to the page, replace the “New Post” button, and append our notice to the notifications' element.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= turbo_stream.append 'posts' do %&gt;
  &lt;%= render @post %&gt;
&lt;% end %&gt;

&lt;%= turbo_stream.replace 'new_post' do %&gt;
  &lt;%= turbo_frame_tag 'new_post' do %&gt;
    &lt;%= link_to 'New post', new_post_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;

&lt;% if notice %&gt;
  &lt;%= turbo_stream.append 'notifications' do %&gt;
    &lt;%= render 'layouts/notice' %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;

&lt;% if alert %&gt;
  &lt;%= turbo_stream.append 'notifications' do %&gt;
    &lt;%= render 'layouts/alert' %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;
</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now, we can create a post, and get the SPA look, without having to resort to rendering everything from JavaScript. </p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":755,"sizeSlug":"full","linkDestination":"media"} -->
<figure class="wp-block-image size-full"><a href="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyMiwicHVyIjoiYmxvYl9pZCJ9fQ==--8f436ee14383f388eaaec0881e0d2c201dbbb0e0/02-Hotwire-Turbo-Notifications.gif"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyMiwicHVyIjoiYmxvYl9pZCJ9fQ==--8f436ee14383f388eaaec0881e0d2c201dbbb0e0/02-Hotwire-Turbo-Notifications.gif" alt="" class="wp-image-755"></a><figcaption class="wp-element-caption">The whole notification look</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>It’s HTML Over The Wire at its best.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Stylizing ActionMailers with Maizzle</title>
      <link>https://onrails.blog/2022/09/13/stylizing-actionmailers-with-maizzle</link>
      <guid isPermaLink="true">https://onrails.blog/2022/09/13/stylizing-actionmailers-with-maizzle</guid>
      <pubDate>Tue, 13 Sep 2022 14:58:35 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>I’ve been using Tailwind CSS for all my new projects, and I’ve been migrating my other projects over slowly. With Tailwind’s Rails gem, it’s been incredibly easy to drop in Tailwind, include my old CSS, and then move pages over. Tailwind is incredibly deferential to other CSS frameworks because it has a unique utility system, rather than trying to redefine the <code>.button</code> class yet again. Maizzle is a tool, written in Javascript, that takes Tailwind styled HTML for a mailer, and generates hardcoded styles. It optimizes for inboxes, so it makes some default decisions, such as column width. If you have an existing theme in your app, you can easily customize all your mailers to match your website’s theme and design.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">How I’m using it</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>I purchased one of the theme packs from the <a href="https://craftingemails.com/email-templates">craftingemails</a> as a great starting point. One nice thing about Maizzle is that you can bring your CSS, and it will add the styles so that it looks correct in as many email clients as possible.  I added my theme colors to make the templates match my project. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Setting it up</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>If you’ve already set up Rails on your machine, with Tailwind CSS, you should have the correct version of node setup. You can follow the directions at <a href="https://maizzle.com/docs/introduction">https://maizzle.com/docs/introduction</a>, but here’s what I did.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>First, download the <a href="https://craftingemails.com/email-templates">email template</a> or the <a href="https://github.com/maizzle/maizzle">starter version from GitHub</a>. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Then, install maizzle and the dependencies:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ npm install</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then, start the development server, which watches the source folder, and builds them.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ maizzle serve</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The one change necessary is to change the <code>baseURL</code> in the <code>config.production.js</code> file. Set it to an empty string so that nothing is prepended for image tags or links.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">module.exports = {
  baseURL: "",
  // ... other options
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Designing the templates</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Since Maizzle just uses CSS, we can take a basic HTML template, and make it look great. This means you’ll want to change the <code>views/layouts/mailer.html.erb</code> to get rid of all the boilerplate to just 1 line:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= yield %&gt; </pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>All the header and HTML tags will be in each email template.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>When designing the template in the maizzle folder, you’ll need to create dummy data to fill in. When you want to replace the data in the actual template that you’ll copy into your Rails app, you could manually add the ERB tags. You could also use Maizzle’s built-in preprocessors to use dummy data for development and actual ERB tags in the production build. For example, the Maizzle starter on GitHub has a transactional template that includes the logo. In Rails, we’ll want to use the <code>asset_path</code> to the icon instead of a local path. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here is the icon:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose"> &lt;img src="images/maizzle.png" width="70" alt="Maizzle" class="max-w-full align-middle [border:0]"&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>If we test the <code>page.env</code> value, we can select the right tags:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;if condition="page.env === 'production'"&gt;
     &lt;img src="&lt;%= asset_path('maizzle.png') %&gt;" width="70" alt="Maizzle" class="max-w-full align-middle [border:0]"&gt;
&lt;/if&gt;
&lt;if condition="page.env !== 'production'"&gt;
     &lt;img src="images/maizzle.png" width="70" alt="Maizzle" class="max-w-full align-middle [border:0]"&gt;
&lt;/if&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Now, you can see the image when you’re designing the template, but when it’s built for distribution, it includes the ERB tag, so you can copy and paste it into your mailer’s view, without have to tweak anything.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Occasionally, you don’t mind the ERB tags in the design, so you can just add them and know they’ll be converted on the Rails side.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":739,"linkDestination":"none"} -->
<figure class="wp-block-image aligncenter"><img src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyNiwicHVyIjoiYmxvYl9pZCJ9fQ==--f63dbb33197e49a171031b34059d45144bb2caec/DraggedImage.png" alt="Example of ERB tags in the output during design" class="wp-image-739"><figcaption class="wp-element-caption">Example of ERB tags in the output during design</figcaption></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Move Into Rails</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Once you’ve got your design working, you should run the build command:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">$ maizzle build production</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>This will optimize the CSS in the templates, and perform any switching we needed for the Rails side. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Look for the template in the <code>dist</code> folder. Copy all the HTML from the template, and the preview the email with your app’s <a href="https://guides.rubyonrails.org/action_mailer_basics.html#previewing-emails">Rails mailer previews</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Fancy Email Templates</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now, you have prettier looking email templates generated with your Tailwind theme. This should make all your transactional emails feel on brand when you correspond with your customers.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Stimulus.js &amp; HOTWire Tutorial: Interactive Deletes</title>
      <link>https://onrails.blog/2022/06/24/stimulus-js-hotwire-tutorial-interactive-deletes</link>
      <guid isPermaLink="true">https://onrails.blog/2022/06/24/stimulus-js-hotwire-tutorial-interactive-deletes</guid>
      <pubDate>Fri, 24 Jun 2022 10:48:35 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Interactive websites have that feeling of immediacy. Clicked links respond in milliseconds, and there is never a need to <em>wait</em>… Waiting for a remote record to delete, and then the whole page refresh afterwards can feel like an eternity in web’s modernity. But a little Stimulus works well with Turbo to make items on a page disappear immediately, all while a network request happens in the background. This tutorial takes a remote deletion link, adds an in-place confirmation message, and then hides the element when the delete request is sent to the server. When the request comes back with a success, the element is removed from the page, or with an error, the element put right back in place.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The controller is going to listen for <a href="https://turbo.hotwired.dev/reference/events">a few different events emitted by Turbo</a><sup><a id="ffn1" href="#fn1">1</a></sup> when the delete link is clicked. It stops the remote request the first time the link is clicked, and puts a confirmation message in the link. This feels better than the usual full alert modal that adding confirmation message usually throws up. Clicking the link a second time lets the request go back to the server. The element’s style is then set to <code>display:none;</code> so that it disappears immediately. A successful Turbo Stream response removes the element entirely, and an unsuccessful response reverts the style, so the element appears again. A timeout is added after the first click to reset the state of the controller and the link if it isn’t clicked a second time.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">The HTML</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>I’m working with a simple ActiveRecord <code>Post</code> model which has a title, author, and text. The controller is similar to what you could generate from the Rails scaffold command. On the <code>index.html.erb</code> view, this is the HTML:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">  &lt;h1 &gt;All Posts&lt;/h1&gt;
  &lt;%= link_to "New Post", new_post_path %&gt;
  &lt;% @posts.each do |post| %&gt;
    &lt;div id="&lt;%= dom_id post %&gt;"
        data-controller="delete"
        data-delete-message-value="&lt;strong&gt;Are you sure?&lt;/strong&gt;"&gt;
      &lt;p&gt;
        &lt;h2&gt;&lt;%= link_to post.title, post %&gt;&lt;/h2&gt;
        &lt;strong&gt;&lt;%= post.author %&gt;&lt;/strong&gt;
        &lt;br /&gt;
        &lt;em&gt;&lt;%= post.created_at.strftime("%l:%M %P") %&gt;&lt;/em&gt;
        &lt;br /&gt;
        &lt;%= link_to "Delete", post_path(post), data: { 'turbo-method': "delete", 'delete-target': "link", action: "turbo:click-&gt;delete#captureClick turbo:before-fetch-request@window-&gt;delete#deleteAndHide" } %&gt;
      &lt;/p&gt;
    &lt;/div&gt;
  &lt;% end %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p><br>There is a <code>div</code> element for the delete controller. The delete link towards the bottom is a target of the delete controller, since the controller needs to use it when getting click events and manipulate the message on it. The controller listens for the <code>turbo:click</code> and the <code>turbo:before-fetch-request</code> events so that the controller can add our visual interactivity. The controller doesn’t need to change anything else with how Rails sends and receives the delete request, meaning less work for the Stimulus controller and fewer chances for bugs.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">The Stimulus Controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>delete_controller.js</code> Stimulus controller is the component in charge of the delete actions. It keeps track of the click state, changes the link text, and handles the result of the server’s response.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>On <code>connect()</code>, the controller sets its delete state to <code>false</code>. This dictates later on how the click handler behaves. It sets a value called <code>clickedController</code> to <code>false</code>. This is used to keep state between the <code>turbo:click</code> event and the <code>turbo:before-fetch-request</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In Turbo, the click event allows Javascript to cancel a Turbo request, and have the browser send the request normally. The controller uses it know that a click has happened, because the the <code>turbo:before-fetch-request</code> event is called on the document, and that’s the event similar to the UJS <code>ajax:success</code> event that the controller will use to cancel the request and change the confirmation message.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>On a call to <code>deleteAndHide(event)</code> when delete is false, the controller records the current delete link text, changes the delete link to a new confirmation message, sets a reset timeout, and then stops the click event. On a call to <code>deleteAndHide(event)</code> when delete is true, the controller hides its element, and adds listeners for the server response. The response proceeds back to the server as usual.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The <code>handleResponse(event)</code> method resets the element back to the way the element looked, and makes the element visible again. It doesn’t do anything on success, because Turbo will remove it based on the server response.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The <code>resetState()</code> method removes the event listeners, sets the delete link back to the original test, and resets the delete state back to false.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">const RESET_TIMEOUT_MILLIS = 3000;

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["link"];
  static values = { message: String };

  connect() {
    this.delete = false;
    this.clickedController = false;
  }

  async captureClick(event) {
    this.clickedController = this.linkTarget == event.target;
  }

  async deleteAndHide(event) {
    if (this.clickedController) {
      this.clickedController = false;
      if (this.delete) {
        this.element.style = "display: none;";
        document.addEventListener(
          "turbo:before-fetch-response",
          this.handleResponse.bind(this)
        );
      } else {
        this.oldMessage = this.linkTarget.innerHTML;
        this.linkTarget.innerHTML = this.messageValue;
        this.delete = true;

        this.timeout = setTimeout(() =&gt; {
          this.resetState();
        }, RESET_TIMEOUT_MILLIS);
        event.preventDefault();
        return false;
      }
    }
  }

  handleResponse(event) {
    clearTimeout(this.timeout);
    this.resetState();
    console.log(this);
    console.log(this.element);
    if (event.detail.fetchResponse.response.status != 204) {
      this.element.style = "";
    }
  }

  resetState() {
    if (this.delete) {
      document.removeEventListener(
        "turbo:before-fetch-response",
        this.handleResponse.bind(this)
      );
      this.linkTarget.innerHTML = this.oldMessage;
      this.delete = false;
    }
  }
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Ruby Controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The delete method on the rails controller needs to respond to the turbo stream format. This is accomplished by adding <strong><code>format.turbo_stream</code></strong>. It’s using a nifty inline render of the <code>turbo_stream</code> remove command, so we don’t need to write an ERB partial.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">  def destroy
    @post.destroy

    respond_to do |format|
      format.turbo_stream { render turbo_stream: turbo_stream.remove(@post) }
    end
  end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Practice?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Try to add something to alert the deleter on the page when the deletion fails. It can assume that failure isn’t a likely case, so the error message should be very disruptive to the page flow.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Interactivity</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>By making actions on a web page feel immediate, web app users feel more confident that the app registered their wishes. Deletion is definitely a good spot for double checking about someone’s intention, but by changing the delete link text, instead of an alert, it’s not as disruptive as usual. Imagine having to delete hundreds of records, and having to move the cursor multiple times for each action, and you’ll see this method is better for everyone.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Want To Learn More?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Try out some more of my <a href="https://johnbeatty.co/stimulus-js-tutorials/">Stimulus.js Tutorials</a>. This is an update of previous tutorial that used Rails UJS, <a href="https://onrails.blog/2019/02/27/stimulus-js-tutorial-interactive-deletes-with-rails-ujs/">Stimulus.js Tutorial: Interactive Deletes with Rails UJS</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol id="footnotes"><!-- wp:list-item -->
<li><a href="https://turbo.hotwired.dev/reference/events">https://turbo.hotwired.dev/reference/events</a> <a href="#ffn1">↩︎</a></li>
<!-- /wp:list-item --></ol>
<!-- /wp:list -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Interactive HOTWired paginated deletes</title>
      <link>https://onrails.blog/2022/06/01/interactive-hotwired-paginated-deletes</link>
      <guid isPermaLink="true">https://onrails.blog/2022/06/01/interactive-hotwired-paginated-deletes</guid>
      <pubDate>Wed, 01 Jun 2022 20:16:26 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>When you have a lot of records, it makes sense to paginate the table. That way, instead of loading thousands of records, you can load a subset, with better performance, and allow your user to move through the pages as leisure. It’s also better for usability, because it’s easier to go back to a particular page rather than scroll through a long list.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I like to use <a href="https://github.com/basecamp/geared_pagination">Basecamp’s <code>geared_pagination</code></a> gem to manage the pagination, offsets, and current page. I’ve found that when I want to delete a particular record, I could the HOTWire or javascript to remove the individual row. But it wouldn’t update the page counts, or make add in another record. This was a usability problem when I had to delete lots of rows. I would eventually remove all the rows on the page, and then I had to refresh to pull more records from the database.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I realized I can use <a href="https://turbo.hotwired.dev/reference/frames">Turbo Frames</a> to replace the entire table, which will update the page counts, and add data back on to the page. Here is how it works below:</p>
<!-- /wp:paragraph -->

<!-- wp:video {"id":722} -->
<figure class="wp-block-video"><video controls="" src="/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTAyMCwicHVyIjoiYmxvYl9pZCJ9fQ==--0fb93a5a1b13da9cd2bba9783cc8f4a6928f2c4c/01-Hotwire-Paginated-Delete.mp4"></video><figcaption class="wp-element-caption">Interactive deletes on a paginated table using HOTWire</figcaption></figure>
<!-- /wp:video -->

<!-- wp:paragraph -->
<p>Here’s how it all works. This tutorial assumes Rails 7, and HOTWire configured with the <a href="https://github.com/hotwired/turbo-rails"><code>turbo-rails</code> gem</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">The Controller</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>I’m going to use a Book model, but you can really use any of your records. Here is the <code>books_controller.rb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose"># frozen_string_literal: true

class BooksController &lt; ApplicationController
  def index
    set_page_and_extract_portion_from Book.all
  end

  def destroy
    @book = Book.find params[:id]
    @book.destroy

    set_page_and_extract_portion_from Book.all
  end
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>index</code> method will use Geared pagination’s method to extract a selection from all the Book records. The <code>destroy</code> method is going to destroy the record, ad you would expect, and then it’s going to call the pagination method again. This will bring up the records we’ll use in the <a href="https://turbo.hotwired.dev/reference/streams">Turbo Stream</a> to replace the table.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":1} -->
<h1 class="wp-block-heading">The Views</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Since there are two actions in the controller, we’ll use two corresponding <code>erb</code> files. First, here is a partial that will be shared between the two actions, <code>_book_records.html.erb</code>:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;% unless page.first? %&gt;
  &lt;%= link_to "Prev", books_path(page: page.number - 1 ) %&gt;
&lt;% end %&gt;
&lt;span&gt;
  Showing page &lt;%= page.number %&gt; of &lt;%= page.recordset.page_count %&gt;
  (&lt;%= page.recordset.records_count %&gt; total)
&lt;/span&gt;
&lt;% unless page.last? %&gt;
  &lt;%= link_to "Next", books_path(page: page.next_param) %&gt;
&lt;% end %&gt;
&lt;table class="table"&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Title&lt;/th&gt;
      &lt;th&gt;Author&lt;/th&gt;
      &lt;th&gt;Publisher&lt;/th&gt;
      &lt;th&gt;Category&lt;/th&gt;
      &lt;th&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;% page.records.each do |book| %&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;%= book.title %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= book.author %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= book.publisher %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= book.category %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= link_to "Delete", book_path(book, page: page.number), data: { 'turbo-method': :delete, 'turbo-confirm': "Are you sure?" } %&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;% end %&gt;
  &lt;/tbody&gt;
&lt;/table&gt; </pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The first part of this partial is the pagination code that moves the page back and forth. The recordset count will show how many records are left, so we can confirm that a Book was deleted successfully. The <code>Delete</code> link will include the current page in the request so the paginate method will load the correct set of records. It also uses the <code>turbo-method</code> and the <code>turbo-confirm</code> data attributes, which are different from the traditional Rails UJS data annotations. They behave the same, but will be used by Turbo instead of Rails UJS.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The <code>index.html.erb</code> will have a <code>turbo_frame_tag</code> so that Turbo knows which section to replace. The <code>action</code> is supposed to update the page’s URL so that when the page reloads, it shows the proper page of records.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;h1 class="title"&gt;Paginate Through Books&lt;/h1&gt;
&lt;%= turbo_frame_tag "books", action: "advance" do %&gt;
  &lt;%= render partial: 'book_records', locals: {page: @page} %&gt;
&lt;% end %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The <code>destroy.turbo_stream.erb</code> will use the replace command to reload the page of records. There is no need to remove the destroyed Book, because it is no longer in the database and won’t appear.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= turbo_stream.replace "books" do %&gt;
  &lt;%= turbo_frame_tag "books", action: "advance" do %&gt;
    &lt;%= render partial: 'book_records', locals: {page: @page} %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Notice how both actions use the <code>book_records</code> partial and pass the <code>@page</code> variable as a local parameter.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Amazing Interactivity</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>One of the great things about HOTWire is how very little changes in the controllers and views. By rethinking how the destroy method is used, we can get a very interactive app without having to build any front end JavaScript.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>Rails PWAs using Turbo HHNPWA #7</title>
      <link>https://onrails.blog/2021/03/02/rails-pwas-using-turbo-hhnpwa-7</link>
      <guid isPermaLink="true">https://onrails.blog/2021/03/02/rails-pwas-using-turbo-hhnpwa-7</guid>
      <pubDate>Tue, 02 Mar 2021 20:53:02 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Does your app even need to be a Progressive Web App? The answer lies in what is missing from your web app.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Would it help to have some information available in the situation where no internet connection is available?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Do you need to be available immediately, just like a native app?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Progressive Web Apps, even if visited through the normal browser chrome, can provide some nice enhancements to your web app’s experience. Thankfully, the minimum configuration to get this experience is very small. If you’ve been following along, you’ll see we started with a regular, performant and interactive Rails app, and only now are we going to turn on the PWA switch.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Setting it up</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>What makes any web site a progressive web app? Two small pieces tell the browser to treat your web page as a progressive web app. You’ll need a:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>JSON Manifest</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>Service worker JavaScript file</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>The browser now grants your app the ability to run a parallel JavaScript process with the ability to inspect and modify all HTTP requests. The biggest advantage of having a service worker is offline access. Offline access stores important information on device, or give your user a small experience of the full online experience. I think the biggest gap in PWAs is that most services require a constant online connection. However, perhaps a Calendar or ToDo service could perform well in this situation. They could keep a cached copy of your events or todos, and then update any changes when the network is available.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Adding the Required Files</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>There are a number of techniques for making your Rails App a PWA. The biggest challenge with the current asset pipeline / webpacker setup is sending the service worker to the client. The PWA specification sets the root of the service worker at the path that it’s served from. If it’s <code>example.org/service-worker.js</code>, then the service worker gets to proxy all traffic for <code>example.org</code>. However, if it’s <code>example.org/packs/js/service-worker.js</code>, then it can <em>only</em> proxy requests under <code>/packs/js/</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Putting the service worker and manifest file as JSON templates lets you use the most of the Rails stack. You can specify images through the asset pipeline, and you can use routing to prefetch pages that should be available online.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This can be done with a controller called <code>service_worker_controller.rb</code> and two methods, like so:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">class ServiceWorkerController &lt; ApplicationController
  protect_from_forgery except: :service_worker

  def service_worker
  end

  def manifest
  end
end</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>The protect from forgery for the <code>service_worker</code> method allows the javascript to be served without any forgery request problems in rails.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><br>Update the routes file:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">get '/service-worker.js' =&gt; "service_worker#service_worker"
get '/manifest.json' =&gt; "service_worker#manifest"</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>In the views folder, make a folder called <code>service_worker</code> and add a <code>manifest.json.erb</code> file that looks something like this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">{
"short_name": "HHNWPA",
"name": "HOTWire Hacker News Progressive Web App",
"icons": [
  {
    "src": "&lt;%= asset_path('icon_192.png') %&gt;",
    "type": "image/png",
    "sizes": "192x192"
  },
  {
    "src": "&lt;%= asset_path('icon_512.png') %&gt;",
    "type": "image/png",
    "sizes": "512x512"
  }
],
"start_url": "&lt;%= root_path %&gt;",
"background_color": "#fff",
"display": "standalone",
"scope": "&lt;%= root_path %&gt;",
"theme_color": "#000"
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Asset paths are available, with means that all the Rails asset pipeline or WebPacker features are available. Those icons referenced above also need to be provided, so if you don’t have them, remove them from the manifest.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now, add a <code>service-worker.js.erb</code> file that looks something like this:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">var CACHE_VERSION = 'v1';
var CACHE_NAME = CACHE_VERSION + ':sw-cache-';

function onInstall(event) {
  console.log('[Serviceworker]', "Installing!", event);
  event.waitUntil(
    caches.open(CACHE_NAME).then(function prefill(cache) {
      return cache.addAll([
        '&lt;%= asset_pack_path 'application.js' %&gt;',
        '&lt;%= asset_pack_path 'application.css' %&gt;',
        '&lt;%= asset_path 'application.css' %&gt;'
      ]);
    })
  );
}

function onActivate(event) {
  console.log('[Serviceworker]', "Activating!", event);
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.filter(function(cacheName) {
          // Return true if you want to remove this cache,
          // but remember that caches are shared across
          // the whole origin
          return cacheName.indexOf(CACHE_VERSION) !== 0;
        }).map(function(cacheName) {
          return caches.delete(cacheName);
        })
      );
    })
  );
}

function onFetch(event) {
  event.respondWith(
    // try to return untouched request from network first
    fetch(event.request).catch(function() {
      // if it fails, try to return request from the cache
      return caches.match(event.request).then(function(response) {
        if (response) {
          return response;
        }
        // if not found in cache, return default offline content for navigate requests
        if (event.request.mode === 'navigate' ||
          (event.request.method === 'GET' &amp;&amp; event.request.headers.get('accept').includes('text/html'))) {
          console.log('[Serviceworker]', "Fetching offline content", event);
          return caches.match('/offline.html');
        }
      })
    })
  );
}

self.addEventListener('install', onInstall);
self.addEventListener('activate', onActivate);
self.addEventListener('fetch', onFetch);</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Whatever files need caching are added in the <code>onInstall</code> function. This template only has the webpack pack files and the asset pipeline CSS, but any images, or asset pipeline files that should be cached can be added here.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Loading the service worker with Stimulus</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>I’ve found a Stimulus controller can load the Service Worker, and it neatly keeps JavaScript out of your other ERB templates. It also provides a cleaner JavaScript communication point between your HTML and the Service Worker.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"xml","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;div data-controller="service-worker"&gt;
  &lt;!-- HTML Code --&gt;
&lt;/div&gt; </pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>It’s weird to say it, since this is the seventh tutorial on this website, but we’ll need to install Stimulus.</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"bash","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">rails webpacker:install:stimulus</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>In the <code>connect()</code> function, the controller sees if the Service worker is running.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If the service worker isn’t running, the controller registers the service worker, and sets up a chain of callbacks, waiting for the installation and activation to complete.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here is <code>service_worker_controller.js</code></p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"jscript","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">import { Controller } from "stimulus";
export default class extends Controller {
  connect() {
    if (navigator.serviceWorker) {
      if (navigator.serviceWorker.controller) {
        // If the service worker is already running, skip to state change
        this.stateChange();
      } else {
        // Register the service worker, and wait for it to become active
        navigator.serviceWorker
          .register("/service-worker.js", { scope: "./" })
          .then(function (reg) {
            console.log("[Companion]", "Service worker registered!");
            console.log(reg);
          });
        navigator.serviceWorker.addEventListener(
          "controllerchange",
          this.controllerChange.bind(this)
        );
      }
    }
  }

  controllerChange(event) {
    navigator.serviceWorker.controller.addEventListener(
      "statechange",
      this.stateChange.bind(this)
    );
  }

  stateChange() {
    // perform any visual manipulations here
  }
}</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Testing?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Google’s <a href="https://github.com/GoogleChrome/lighthouse">Lighthouse</a> is the best way to test how an app matches the specification. Lighthouse will give recommendations, and list what’s missing. It can even test accessibility. Progressive Web Apps need HTTPS when not being served from localhost, so testing in Google Chrome during development is the easiest option.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Next Steps</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Subscribe for updates as this project takes shape. You can see all the code at Github: <a href="https://github.com/OnRailsBlog/hhnpwa">https://github.com/OnRailsBlog/hhnpwa</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Other HOTWire Tutorials</h2>
<!-- /wp:heading -->

<!-- wp:list -->
<ul><!-- wp:list-item -->
<li><a href="https://onrails.blog/2020/12/23/building-hhnpwa-1-setting-up-for-top-stories/">HOTWire HNPWA Tutorial #1: Setting up for Top Stories</a></li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li><a href="https://onrails.blog/2020/12/27/building-hhnpwa-2-streaming-top-items/">HOTWire HNPWA Tutorial #2: Turbo Streaming Top Items</a></li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li><a href="https://onrails.blog/2020/12/28/building-hhnpwa-3-top-item-pagination/">HOTWire HNPWA Tutorial #3: Paginating Top Items</a></li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li><a href="https://onrails.blog/2020/12/29/russian-doll-caching-building-hotwire-hnpwa-4/">HOTWire HNPWA Tutorial #4:Russian Doll Caching</a></li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li><a href="https://onrails.blog/2021/01/07/lazy-loading-lots-of-comments-hotwire-tutorial-5/">HOTWire HNPWA Tutorial #5: Lazy Loading Lots of Comments</a></li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li><a href="https://onrails.blog/2021/01/22/improving-performance-with-russian-doll-caching-hotwire-tutorial-6/">HOTWire HNPWA Tutorial #6: Improving Performance with Russian Doll Caching</a></li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li><a href="https://onrails.blog/2021/03/02/rails-pwas-using-turbo-hhnpwa-7/">HOTWire HNPWA Tutorial #7: Adding PWA Service Worker</a> </li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>HOTWiring an existing Rails Monolith: Forms!</title>
      <link>https://onrails.blog/2021/02/18/hotwiring-an-existing-rails-monolith-forms</link>
      <guid isPermaLink="true">https://onrails.blog/2021/02/18/hotwiring-an-existing-rails-monolith-forms</guid>
      <pubDate>Thu, 18 Feb 2021 14:20:23 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>Let’s say your majestic monolith is looking for more interactivity, and you’ve picked up <a href="https://turbo.hotwire.dev">Turbo</a>. What aspects of a Rails app change that may cause some headaches? <a href="https://turbo.hotwire.dev/handbook/installing">Add Turbo</a>, and then follow along!</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading">Forms</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>First, you need to test all your forms. Turbo dramatically changes the behavior of each form. The biggest change you will see is that your form redirects don’t behave the same way. Since Turbo is adding Single Page Application (SPA) functionality to your app, you will likely need to reengineer the frontend to behave more like a SPA. I think the quickest way to get Turbo working, and give you flexibility to incrementally update your forms is turn off remote forms, and tell Turbo to ignore the form. This worked best on my forms that made some change, and then redirected to a new page. This meant adding some <code>data-turbo</code> and <code>data-remote</code> attributes, like so:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">&lt;%= form_with model: @order, url: orders_path, data: { turbo: false, remote: false } do |form| %&gt;</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Setting the remote and turbo data attributes to false makes the form submission occur outside the JavaScript environment. This will blow away the JavaScript environment, so you’ll want to figure out a way reengineer your forms, which I’ll show you in the next section.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I don’t want to indict Turbo at all for this change. Adding Turbo is a rethinking of the way your Rails app interacts with the browser, and this is a stop gap to your existing app works, and you can refactor the front end to include more interactivity.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
    <item>
      <title>HOTWiring an Existing Rails Monolith</title>
      <link>https://onrails.blog/2021/01/28/hotwiring-an-existing-rails-monolith</link>
      <guid isPermaLink="true">https://onrails.blog/2021/01/28/hotwiring-an-existing-rails-monolith</guid>
      <pubDate>Thu, 28 Jan 2021 09:14:21 +0000</pubDate>
      <content:encoded>
        <![CDATA[<!-- wp:paragraph -->
<p>How do you add <a href="https://turbo.hotwire.dev">Turbo</a> to your existing Rails app? What do you need to watch out for as you transition to a full HOTWire approach? I found it to be very straight forward, and mostly a search and replace operation.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Add <a href="https://github.com/hotwired/turbo-rails">Turbo Rails gem</a> to the Gemfile and remove Turbolinks:</p>
<!-- /wp:paragraph -->

<!-- wp:syntaxhighlighter/code {"language":"ruby","className":"not-prose"} -->
<pre class="wp-block-syntaxhighlighter-code not-prose">gem 'turbo-rails'</pre>
<!-- /wp:syntaxhighlighter/code -->

<!-- wp:paragraph -->
<p>Then run <code>./bin/bundle install</code> and</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>run <code>./bin/rails turbo:install</code></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Search through your code base for <code>turbolinks</code> to see where you are using it. The first change I found was in the html layouts. <code>'data-turbolinks-track'</code> became <code>'data-turbo-track'</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You may have configured some links that you didn’t want Turbolinks to follow, by setting the <code>data-turbolinks</code> attribute to false. This attribute is now <code>data-turbo</code>, so look for those throughout your code base.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You may have used the permanent attribute, <code>data-turbolinks-permanent</code>, which kept elements between page visits, and mimicked the behavior of single page applications. This attribute is now <code>data-turbo-permanent</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you have any JavaScript code that listened for <code>turbolinks</code> events, you should make sure the names of the events now start with <code>turbo</code> instead of <code>turbolinks</code>. I have a couple Stimulus controllers that listen to the <code>turbolinks:before-visit</code> event, so those are now <code>turbo:before-visit</code>. You can see all the <a href="https://turbo.hotwire.dev/reference/events">Turbo events</a> and compare them with the <a href="https://github.com/turbolinks/turbolinks#full-list-of-events">Turbolinks events</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Of course, run your tests after all these changes, and QA all your pages to make sure nothing is broken.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I’m looking forward to taking advantage of more of the features of Turbo, including lazy loading frames and an easier to use broadcast method for changes on the page.</p>
<!-- /wp:paragraph -->

<!-- wp:mailpoet/subscription-form-block {"formId":1} /-->]]>
      </content:encoded>
      <category>Posts</category>
    </item>
  </channel>
</rss>
