Hotwire Demo — Stimulus

A lightweight JavaScript Framework #HotwireSeries_Episode#3

J3
Jungletronics
7 min readAug 26, 2023

--

This post is a continuation of this one.

In this post :

. Learn about Stimulus.

Stimulus is a JavaScript framework that focuses on enhancing interactivity in web applications by adding behavior to HTML elements. It’s designed to be a lightweight and pragmatic approach to building dynamic web interfaces. Stimulus is created by the same team behind Basecamp, a popular project management and collaboration tool.

Key features and concepts of Stimulus include:

  1. Minimalism: Stimulus aims to be minimalistic and easy to learn. It doesn’t require a deep understanding of complex JavaScript concepts or a large ecosystem of third-party libraries.
  2. HTML-Centric: Stimulus encourages you to keep the behavior and presentation logic of your application closer to the HTML markup. This makes it easier to maintain and understand your codebase.
  3. DOM Manipulation: Instead of replacing or re-rendering entire sections of the DOM, Stimulus focuses on making incremental changes to the existing HTML using data attributes and JavaScript actions.
  4. Controller-Based: Stimulus introduces the concept of controllers, which encapsulate behavior related to specific parts of the HTML. Each controller is associated with a specific HTML element using a data-controller attribute.
  5. Actions: Controllers in Stimulus have actions — methods that are triggered by events on the associated HTML elements. For instance, you can define an action to run when a button is clicked or a form is submitted.
  6. Targets: Stimulus provides a way to reference elements within a controller’s scope using targets. This makes it easy to interact with specific parts of the HTML from your JavaScript code.
  7. Data Attributes: Stimulus relies on data attributes (such as data-action and data-target) to specify the behavior and relationships between elements and controllers.
  8. Progressive Enhancement: Stimulus follows a progressive enhancement approach, meaning your application’s core functionality is available even if JavaScript is disabled or unavailable. Stimulus then enhances the experience by adding interactivity when JavaScript is present.

Overall, Stimulus is a suitable choice for projects that want to introduce interactivity without adopting more complex and heavy front-end frameworks like React or Angular. It’s especially beneficial for applications where traditional server-side rendering plays a significant role.

0#Step — Download the last code and prepare your Vscode.

In the last post, the message displayed by the field is not being cleared. Let’s employ Stimulus to rectify this issue!

The message displayed by the field is not being cleared. Employ Stimulus to rectify this issue!

1#Step — Create:

chat/app/javascript/controllers/reset_form_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
reset() {
this.element.reset()
}
}

This Stimulus controller adds behavior to an HTML element. When the reset action is triggered on that element (e.g. when a button is clicked), the form fields within the associated HTML form element are reset, clearing their values.

2#Step — Then Add to: chat/app/views/messages/new.html.erb this fragment:

,data:{ controller:”reset-form”, action: “turbo:submit-end->reset-form#reset”}

Please be aware that the controller file is named reset_form_controller.js (with underscores), while when referencing it, reset-form should be used with dashes.

Here is how it results:

<h1>New Message</h1>
<turbo-frame id="new_message" target="_top">
<%= form_with(model: [@message.room, @message], data:{ controller:"reset-form", action: "turbo:submit-end->reset-form#reset"}) do |form| %>
<div class="field">
<%= form.text_field :content %>
<%= form.submit "Send"%>
</div>
<% end %>
</turbo-frame>
<%= link_to 'Back', @message.room %>

The behavior defined in the Stimulus controller is clearing form fields in response to the form submission.

Now the message displayed by the field is being cleared. Thanks to the Stimulus method reset()!

3#Step — Let’s start a conversation with another window:

Right now, on the other side, we have to reload to see what`s been said. Let’s fix it!

4#Step — Let’s establish a WebSocket connection to the stream identified by the Room we’re in.

This is done by turbo_stream_from tag.

GoTo:

chat/app/views/rooms/show.html.erb

Add this line:

<%= turbo_stream_from @room %>

See the final result:

<p id="notice" style="color: green"><%= notice %></p>
<%= turbo_stream_from @room %>
<turbo-frame id="room">
<%= render @room %>
<p>
<%= link_to "Edit", edit_room_path(@room) %>
<%= link_to "Back", rooms_path, "data-turbo-frame":"_top" %>
</p>
</turbo-frame>
<div id="messages">
<%= render @room.messages %>
</div>
<%# <%= link_to "New Message", new_room_message_path(@room) %>
<turbo-frame id="new_message" src="<%= @room.id %>/messages/new" target="_top">
</turbo-frame>

The connection will be made to the Turbo Stream’s channel running over ActionCable from the inclusion of this text: <%= turbo_stream_from @room %>.

Now everything works fine!

Now the twins’ windows update in sync. That’s fine!

See the Terminal:

[ActiveJob] [Turbo::Streams::ActionBroadcastJob] [a002b13a-6189-406e-8dca-197f2b4a955c]   Message Load (0.1ms)  SELECT "messages".* FROM "messages" WHERE "messages"."id" = ? LIMIT ?  [["id", 6], ["LIMIT", 1]]
[ActiveJob] [Turbo::Streams::ActionBroadcastJob] [a002b13a-6189-406e-8dca-197f2b4a955c] Performing Turbo::Streams::ActionBroadcastJob (Job ID: a002b13a-6189-406e-8dca-197f2b4a955c) from Async(default) enqueued at 2023-08-26T21:54:15Z with arguments: "Z2lkOi8vY2hhdC9Sb29tLzE", {:action=>:append, :target=>"messages", :targets=>nil, :locals=>{:message=>#<GlobalID:0x00007fc04409d628 @uri=#<URI::GID gid://chat/Message/6>>}, :partial=>"messages/message"}
[ActiveJob] [Turbo::Streams::ActionBroadcastJob] [a002b13a-6189-406e-8dca-197f2b4a955c] Rendered messages/_message.html.erb (Duration: 0.6ms | Allocations: 195)
[ActiveJob] [Turbo::Streams::ActionBroadcastJob] [a002b13a-6189-406e-8dca-197f2b4a955c] [ActionCable] Broadcasting to Z2lkOi8vY2hhdC9Sb29tLzE: "<turbo-stream action=\"append\" target=\"messages\"><template><p id=\"\">\n 26 Aug 21:54: Hi From Left Window!\n</p>\n</template></turbo-stream>"
[ActiveJob] [Turbo::Streams::ActionBroadcastJob] [a002b13a-6189-406e-8dca-197f2b4a955c] Performed Turbo::Streams::ActionBroadcastJob (Job ID: a002b13a-6189-406e-8dca-197f2b4a955c) from Async(default) in 6.04ms
Turbo::StreamsChannel transmitting "<turbo-stream action=\"append\" target=\"messages\"><template><p id=\"\">\n 26 Aug 21:54: Hi From Left Window!\n</p>\n</template></turbo-stream>" (via streamed from Z2lkOi8vY2hhdC9Sb29tLzE)
Turbo::StreamsChannel transmitting "<turbo-stream action=\"append\" target=\"messages\"><template><p id=\"\">\n 26 Aug 21:54: Hi From Left Window!\n</p>\n</template></turbo-stream>" (via streamed from Z2lkOi8vY2hhdC9Sb29tLzE)

5#Step — Let’s send a new Message to this stream by adding a broadcast call to the Message creation. This method call mirrors what we already doing in the Turbo Stream template, just over WebSocket now!

GoTo: Add broadcasts to chat/app/models/room.rb:

class Room < ApplicationRecord
has_many :messages
broadcasts
end

Note: Comment on this line <%= turbo_stream.append “messages”, @message %> on app/views/messages/create.turbo_stream.erb. Now the duplication is gone!

6#Step — We can also Turbo Stream deleting messages. We will add a similar model call back and destroy the triggers that remove broadcasts sent to the same stream.

GoTo:

app/models/message.rb

class Message < ApplicationRecord
belongs_to :room
broadcasts_to :room

# after_create_commit -> { broadcast_append_to room }
# after_destroy_commit -> { broadcast_remove_to room }
# after_update_commit -> { broadcast_replace_to room }
end

We can comment out all three lines about CRUD operations and replace them with a single call to the broadcasts_to tag.

Here we can invoke this flow from the console:

Room.first.messages.first.destroy
Room.first.messages.create! content:"Message From The Console!"
  Message Destroy (0.3ms)  DELETE FROM "messages" WHERE "messages"."id" = ?  [["id", 2]]
TRANSACTION (7.8ms) commit transaction
[ActionCable] Broadcasting to Z2lkOi8vY2hhdC9Sb29tLzE: "<turbo-stream action=\"remove\" target=\"message_2\"></turbo-stream>"
=>

7#Step —Let’s add the shortened form directly to changing the Room as well.

Now GoTo:

chat/app/views/rooms/_room.html.erb

Modify it to:

<p id="<%= dom_id room %>">
<strong>Name:</strong>
<%= room.name %>
</p>
Now you can edit the name in one window and instantly see it updated in the other!

So that is Hotwire, an alternative approach to building modern web applications without using much JavasScript by sending HTML instead of JSON over the wire.

We get to keep all our template rendering on the server, which means writing more of our applications in our favorite programming languages.

There you have it!

👉️GitHub — Hotwire_v4

Related Posts:

0#Episode — HotwireSeries — Intro — Hotwire Demo — What is the purpose of Hotwire in Rails 7?

1#Episode — HotwireSeries — Turbo Frame — Decomposes Pages Into Independent Contexts

2#Episode — HotwireSeries — Turbo Stream — Pushing HTML Updates To The Client Using WebSockets

3#Episode — HotwireSeries — Stimulus — A lightweight JavaScript Framework (this one)

GitHub cmds:

git status
git add -A
git remote -v
git commit -m ":sparkles: feat: Add Stimulus"
git tag -a Hotwire_v4 -m "Turbo Tech - How it Works! Go to https://hotwired.dev/" -m "0- Work with Stimulus." -m "Thank you for downloading this project 😘️👌️👋️😍️"
git push origin Hotwire_v4

Note about ImportMap:

The new Rails Stimulus integration gem ships with an autoloader for your controller

This is done with an import map supported by ES-Molule-Shim

Go To:

chat/config/importmap.rb

pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true

The code snippet we provided is related to the Hotwire ecosystem, specifically the Stimulus framework. Let’s break down the code to understand its components:

  1. pin: This seems to be a directive or configuration used within the Hotwire framework, specifically related to Stimulus. In Stimulus, you can use the pin directive to indicate that a certain controller should be "pinned" to a specific JavaScript file.
  2. "@hotwired/stimulus-loading": This is likely the name of a Stimulus controller. Stimulus is a JavaScript framework that focuses on enhancing interactivity in web applications by adding behavior to HTML elements. "@hotwired/stimulus-loading" could be the name of a controller related to loading behavior.
  3. to: "stimulus-loading.js": This part specifies the JavaScript file to which the Stimulus controller should be pinned. The file name here is "stimulus-loading.js". This file likely contains the code for the specified controller's behavior.
  4. preload: true: This indicates that the JavaScript file should be preloaded. Preloading allows the browser to fetch the JavaScript file earlier in the page loading process, which can help improve performance.

Overall, this code snippet appears to be configuring a Stimulus controller named "@hotwired/stimulus-loading" to be associated with a JavaScript file named "stimulus-loading.js" and specifying that this file should be preloaded.

About JavaScript:

What is shims and ES module?

http://[::1]:3000/assets/es-module-shims.min-4ca9b3dd5e434131e3bb4b0c1d7dff3bfd4035672a5086deec6f73979a49be73.js ?

The URL http://[::1]:3000/assets/es-module-shims.min-4ca9b3dd5e434131e3bb4b0c1d7dff3bfd4035672a5086deec6f73979a49be73.js pointing to a JavaScript file named es-module-shims.min-4ca9b3dd5e434131e3bb4b0c1d7dff3bfd4035672a5086deec6f73979a49be73.jslocated in the “assets” directory on the local host or server.

The name es-module-shims.min is related to ES (ECMAScript) module shims or polyfills.

ES modules are a way to organize and share JavaScript code, and shims or polyfills are often used to provide compatibility for newer features in older browsers or environments that might not support them natively.

--

--

J3
Jungletronics

Hi, Guys o/ I am J3! I am just a hobby-dev, playing around with Python, Django, Ruby, Rails, Lego, Arduino, Raspy, PIC, AI… Welcome! Join us!