Resources

Hotwire - https://hotwired.dev/
Episode Source Code - https://github.com/driftingruby/369-hotwire-introduction
This episode is sponsored by Honeybadger

Download Source Code

Summary

# Terminal
bin/rails action_text:install
bin/rails g model post title
bin/rails g stimulus search
bin/rails g stimulus search-active

# models/post.rb
class Post < ApplicationRecord
  has_rich_text :content
end

# welcome_controller.rb
class WelcomeController < ApplicationController
  def index
    @posts = params[:query] ? Post.where("title like ?", "%#{params[:query]}%") : []
    respond_to do |format|
      format.html {}
      format.turbo_stream {
        render turbo_stream: turbo_stream.replace("results", partial: "welcome/results")
      }
    end
  end

  def show
    @post = Post.find(params[:id])
    render turbo_stream: turbo_stream.replace("show_content", partial: "welcome/show")
  end
end

# javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="search"
export default class extends Controller {
  connect() {
    this.element.setAttribute("data-action", "keyup->search#search")
  }

  search() {
    let params = new URLSearchParams()
    params.append("query", this.element.value)

    fetch(`/?${params}`, {
      method: "GET",
      headers: {
        Accept: "text/vnd.turbo-stream.html"
      }
    })
      .then(r => r.text())
      .then(html => Turbo.renderStreamMessage(html))
  }
}

# javascript/controllers/search_active_controller.js
import { Controller } from "@hotwired/stimulus"
import { List } from "immutable"

// Connects to data-controller="search-active"
export default class extends Controller {
  connect() {
    this.element.setAttribute("data-action", "click->search-active#clicked")
  }

  clicked() {
    let links = document.querySelectorAll("#results a.active")
    Array.from(links).forEach(link => {
      link.classList.remove("active")
    })

    this.element.classList.add("active")
  }
}

# views/welcome/index.html.erb
<div class="row">
  <div class="col-4">
    <%= form_with url: root_path do |f| %>
      <%= f.text_field :query, class: "form-control", "data-controller": :search %>
    <% end %>
    <%= render "welcome/results" %>
  </div>

  <div class="col-8">
    <%= turbo_frame_tag :show_content %>
  </div>
</div>

# views/welcome/_results.html.erb
<%= turbo_frame_tag :results do %>
  <div class="list-group mt-3">
    <% @posts.each do |post| %>
      <%= link_to post.title,
        welcome_path(post),
        class: "list-group-item list-group-item-action",
        "data-controller": "search-active" %>
    <% end %>
  </div>
<% end %>

# views/welcome/_show.html.erb
<%= turbo_frame_tag :show_content do %>
  <h1><%= @post.title %></h1>
  <%= @post.content %>
<% end %>

# config/routes.rb
Rails.application.routes.draw do
  root to: 'welcome#index'
  resources :welcome, only: :show
end