Introduction to Stimulus
Stimulus is a Javascript framework designed to enhance static or server-rendered HTML by connecting JavaScript objects to elements on the page called "controllers" using simple annotations.
It belongs to a bundle of server-side frameworks and technologies called Hotwire. Stimulus is complementary to the other technologies that compose Hotwire and together can be used to create a responsive user experience while keeping most of the logic on the server side.
Installation
Stimulus, as part of Hotwire, is configured by default in Rails 7+ applications unless the –skip-hotwire flag is passed to the rails new generator.
For older versions of Rails you can use Stimulus with any asset packaging systems or include it from its CDN with a <script>
tag.
How does it work?
It does one thing, but it does it very well: it handles events that occur in the HTML and executes some JavaScript. This is achieved by continuously monitoring the page, looking for data-controller attributes. It then matches each attribute with its corresponding controller, creating a bridge that connects HTML to JavaScript.
In a Stimulus application, the state lives as attributes in the DOM which makes it different from other popular Javascript frameworks like React. The controllers themselves are largely stateless.
Aside from controllers, the three other major Stimulus concepts are:
- actions, which connect controller methods to DOM events using data-action attributes
- targets, which locate elements of significance within a controller
- values, which read, write, and observe data attributes on the controller’s element
We will review each concept in the example below.
Example
Let’s suppose we are building a classic “ToDo” app and that we want to validate from the client side if the “description” field of a “ToDo” is not empty.
First of all, we need to create our Stimulus “todos_controller”. For that, we can run the following Rails generator command:
rails generate stimulus todos
It will create the app/javascript/controllers/todos_controller.js
file which will look like this:
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="todos"
export default class extends Controller {
connect() {
}
}
Now that our controller is created, we will add the necessary tags to connect our form (app/views/todos/_form.html.erb) with the Stimulus controller. Also, to make things easier, we will replace the default “submit” controller with a normal button:
<%= form_with(model: todo, data: { controller: "todos", "todos-target": "form" }) do |form| %>
<% if todo.errors.any? %>
<div style="color: red">
<h2><%= pluralize(todo.errors.count, "error") %> prohibited this todo from being saved:</h2>
<ul>
<% todo.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description, data: { "todos-target": "description" } %>
</div>
<div>
<%= form.button "Create Todos", type: "button", data: { action: "click->todos#validate" } %>
</div>
<% end %>
Within the data
attribute, controller
creates the data-controller
tag we need to specify the Stimulus controller we need. todos-target
specifies that we will reference the form value in the controller.
In the case of the button, data: { action: "click->todos#validate"}
creates the data-action
tag and specifies the action of the “todos” controller that will be executed.
Next, we need to install the @rails/ujs
library that will help us submit the form programmatically. To do that, we simply run the following command:
./bin/importmap pin @rails/ujs
That will add @rails/ujs
to our config/importmap.rb
file. Then we need to add the following code to our app/javascript/controllers/application.js
file:
import Rails from '@rails/ujs';
Rails.start();
After that we need to restart our application.
Finally, we add the necessary code to validate the “description” field of our “ToDo” in our “todos_controller.js” file:
import Rails from "@rails/ujs";
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="todos"
export default class extends Controller {
static targets = ["description", "form"]
validate() {
if (!this.description) {
alert("A description is required.");
return
}
Rails.fire(this.formTarget, 'submit');
}
get description() {
return this.descriptionTarget.value
}
}
The get description()
part allows us to create a “getter” that will allow us to reference this.descriptionTarget.value
as this.description
.
The validate()
function is very simple. It just shows a message to the user if the “description” field comes empty and then immediately returns. If not, it “manually” submits the form referencing the “form” target we defined earlier.
Wrap Up
Stimulus, as part of the Hotwire bundle, offers a powerful yet straightforward way to enhance your Rails applications with dynamic, client-side interactions while keeping the majority of your logic on the server. By leveraging Stimulus controllers, actions, targets, and values, you can create a seamless and responsive user experience without the complexity of a full-fledged front-end framework. Whether you’re building a simple form validation or more intricate user interfaces, Stimulus provides a robust toolset to keep your code clean and maintainable.
Give it a try in your next project and see how it can simplify your development process and enhance your application’s performance!
Need more help or expertise? Contact us today to learn more about our services, and let us support your development needs…