StimulusJS is the new Javascript framework that’s been published this year (2018) by DHH, the creator of Ruby on Rails.
This is a practical guide to start using StimulusJS in a Ruby on Rails project (with a bonus at the end 😉). You’ll also find at the end of the article a link to a project that uses all that’s presented in this article for you to try it out of the box.
You might be tempted to compare it to other frameworks such as ReactJS, Angular or VueJS but you should not. StimulusJS doesn’t play in their field since it doesn’t intend to build user interface components, synchronize frontend and backend using AJAX or any of this stuff.
As of today, StimulusJS’ unique job is to bind behaviors to HTML elements.
A real competitor would be JQuery’s .on() method. JQuery is great but it forces you to define your bindings in Javascript files, which means you can’t know if an HTML element has a bound behavior unless you read all Javascript files. I prefer the StimulusJS approach to it: read an HTML element attributes to know if a behavior is bound or not. Now don’t get me wrong, while I delegate the binding part to StimulusJS, I still use JQuery to make my life easier when it comes to frontend development (AJAX calls, etc.). The two work well together.
A quick word about JQuery and Turbolinks
If you ever used JQuery and Turbolinks together, you’ll be happy to know that there is no more of this:
$(document).ready()
And no more of this:
$(document).on('turbolinks:load', function() {})
StimulusJS takes care of this now :) Put your code in your controllers and let the framework do the magic. What controllers? How? Where? We’ll talk about this in a bit. Let’s first install the dependencies to run StimulusJS in a Ruby on Rails project, then we’ll see how to use it.
A quick word about controllers
This might be obvious to experienced frontend developers but I want to make sure no one gets confused:
Rails has its controllers under app/controllers
and StimulusJS has its controllers under app/javascript/controllers
. There is no link between them, none.
In this article, when I refer to a controller, I’m talking about a StimulusJS one.
Install StimulusJS in Rails using Yarn and Webpacker
1. Install Yarn
Rails shifted to Yarn for Javascript packages management. Yarn has a great community, handles dependencies, provides caching, etc. Now when you need to add an asset dependency to your project (Javascript but also stylesheets), I strongly suggest you first look for the yarn package before you look for the gem.
TL;DR: Bootstrap, MomentJS, ChartJS, Fontawesome, etc.: add them through Yarn.
Select your operating system
Follow the instructions
To add a package, use the searchbox in https://yarnpkg.com/ to find it, then install it using yarn add [package name]
.
2. Install Webpacker
Nowadays, Rails applications are not always just plain Ruby on Rails code. You often have a Rails API, a React/Vue/Angular/YouNameIt Javascript frontend and some SCSS stylesheets to make it all look pretty. In this context, we consider the Javascript frontend part as an app on its own for it often comes with its own controllers, router, models, etc.
There are two things to bare in mind:
Keep your codebase clean. Separate what’s specific to your React/whatever user interface from the rest of your application (i.e. put the JS application code in a specific subdirectory of your Rails project)
Take advantage of the great tools available today such as ES6, SCSS, etc.
Webpack is here to take care of ES6 and SCSS compilation, minification, dependencies management and much more.
Webpacker is the gem that adds Webpack to a Rails project.
When you use webpacker, a new subdirectory is added to your project: app/javascript/packs/.
Each pack is a module you can include in your views. Your StimulusJS application will be a module to include in your views layout (see how in the coming section).
Add
gem 'webpacker'
to your Gemfile and runbundle install
.Run the following command:
rails webpacker:install
Take a quick look at the following files to get a sense of what’s going on:
config/webpacker.rb
config/webpack/*
app/javascript/packs/application.js
3. Install StimulusJS
There’s a pretty handy command to add StimulusJS to your project:
rails webpacker:install:stimulus
This will install StimulusJS, create a directory that’ll contain your controllers and add a couple of lines to your application.js
pack to initialize the framework.
Take a quick look at the following:
app/javascript/packs/application.js
app/javascript/packs/controllers/
Now make Rails include your first pack in app/views/layouts/application.html.erb
. If your pack is app/javascript/packs/application.js
, then add the following:
Hello world example
We’re now ready to move on to the next topic: how to use StimulusJS.
Before we get into how it works, I’d like you to read the following code snippet:
app/views/younameit/index.html.erb
:
app/javascript/controllers/mycontroller_controller.js
:
What it does is pretty straightforward: when you click on the button, it displays “Hello World!”.
Now that you have an idea of how StimulusJS works, let’s break it all down.
Link an HTML element to a StimulusJS controller (data-controller)
The very first step is to link a part of your HTML DOM to a controller.
Create a controller (see the example above or)
Encompass the HTML document subpart you want to link to this controller in an element that’ll have a
data-controller
attribute. For instance:<div data-controller="mycontrollername">...</div>
That’s it. Now this div and everything in it can interact with the mycontrollername
controller.
Don’t fall into this trap
If your controller’s filename contains underscores, the corresponding name to use in your HTML document has underscores (_) replaced by hyphens (-).
Filename:
app/javascript/controllers/content_loader_controller.js
(use underscores)Controller name:
content-loader
(use hyphens)<div data-controller="content-loader">...</div>
Data binding (data-target)
This is the most basic usage of StimulusJS: reference an HTML element in a controller in order to access it later on. We call this a target (a target descriptor to be exact).
First, choose a name for each target and list them in the targets
static array of your controller.
Then add a data-target="controllername.targetname"
attribute to each targeted HTML element.
How to access a target from the controller? We do it by calling, for a target named output
: this.outputTarget
.
Since in our example output
is a span, we can change its content via the textContent
attribute. This means that this.outputTarget.textContent = 'Call me daddy'
will display Call me daddy
in the view.
One target to rule them all
While the basic use case is one target for one HTML element, you actually can have one target for multiple HTML elements. You then access them through the array this.outputTargets
(plural form).
Event-triggered behavior (data-action)
Want to call a method when a particular event (click, keypress, etc.) happens? Use the attribute data-action
on your HTML element.
data-action
, as you can easily guess, allows us to call a controller method on a certain event. In the present example:
The event is
click
The controller is
mycontroller
The method to call is
say_hello
You can listen to many events such as click, dbclick, keyup, etc.
Initialize your controller from the view (data-*)
Sometimes you want to reuse a single controller multiple times in the same view for different elements (a different controller instance for each element).
Let’s say we have a blog with comments. Abusive comments can be reported to the administrator. Each comment shall be displayed and have its own report button. The reporting shall be asynchronous (AJAX).
It is obvious that we need only one StimulusJS controller to provide this simple behavior: catch a click event on a button and call a controller method that’ll do all the AJAX call required to report the abusive comment. We can then have each comment bound to a different instance of the controller.
Since we can’t send an argument in data-action
(<button data-action="click->comment-report#report(41)">...</button>
doesn’t work), we need to figure out how to initialize each instance with the comment id so that the AJAX call is made to the right url.
Once again it’s really simple: when you use data-controller
in an HTML element, you can also add other data-<controller name>-<attribute name>
attributes. For instance data-comment-report-comment-id="42"
:
Note that data-comment-report-comment-id
uses hyphen-separated lowercase words while this.data.get("commentId")
uses lower camelCase syntax.
To sum up
There are three main tools:
data-controller : if your controller file is named
progress_tracker_controller.js
, you do<div data-controller="progress-tracker">...</div>
data-target : for a
name
target in your controller, you do<div data-controller="progress-tracker"><p data-target="progress-tracker.name"></p></div>
and access it usingthis.nameTarget
.data-action : call a method
rule_them_all
when aclick
event arises by adding said method to the controller and<div data-controller="progress-tracker"><p data-action="click->progress-tracker#rule_them_all">Click me</p></div>
There are also these 3 methods that I’m not going to cover in this article, just note that they exist and can come in handy once you get familiar with StimulusJS and need to do some more advanced stuff.
initialize()
is invoked once, when the controller is first instantiated.connect()
: is invoked anytime the controller is connected to the DOM.disconnect()
: is invoked anytime the controller is disconnected from the DOM.
From here, you can do pretty much anything you want. You can import JQuery in your controller and do AJAX calls, change the style of an element, etc. You basically delegate the bindings/events management part to StimulusJS and do the rest with your tools of choice. You can even skip the target part and use JQuery selectors but why bother when StimulusJS does it for you?
Bonus: Add StimulusJS to ActiveAdmin
If you need to provide a better user experience on your ActiveAdmin dashboard, I suggest you add a little sugar to it using Stimulus. The principle is the same: views, controllers, data-*.
We’ve seen how to include a pack in a view layout using javascript_pack_tag
, but how do you include then in ActiveAdmin?
Open config/initializers/active_admin.rb
and add the following:
That’s it for the configuration part!
Here’s a basic example of how to use it in a form:
One thing you might notice is that you need to restart your Rails server for any changes in the controller to be taken into account. This is because the webpack compilation is done only once, at application startup.
One way of fixing this is to remove the lines added to config/initializers/active_admin.rb
and simply add script src: asset_pack_path('application.js')
to each ActiveAdmin form that uses a StimulusJS controller:
Clone this Github repository and start playing with StimulusJS!
Thanks for reading!