How to build an image gallery in Rails with Stimulus

Updated: 

Learn how to create an interactive image gallery in Ruby on Rails using Stimulus.js. Step-by-step tutorial from the Full Stack Rails Mastery course.

This lesson is from Full Stack Rails Mastery.

In this lesson, we’ll create an interactive image gallery for the product page using Stimulus.

Here we have all the thumbnails of the product’s images and the first one is displayed as a large image.
Screenshot 2024-07-23 at 10.05.32.png 428.54 KB

We want to be able to click on any thumbnail to see the large version of it above.
Stimulus is a part of Hotwire. It’s "a modest JavaScript framework for the HTML you already have".
stimulus.png 173.63 KB



Stimulus gives you a lightweight way to add interactivity to webpages.
You start by adding 3 data attributes to your HTML:
  1. controller
  2. target
  3. action
Stimulus automatically connects DOM elements to JavaScript objects called controllers.
The controller is where you define the logic of the interaction.
In the example on the Stimulus homepage, there's a form with an input text field where you can enter a name. When you click greet, it displays the text “Hello” with the entered name.
<div data-controller="hello">
  <input data-hello-target="name" type="text">

  <button data-action="click->hello#greet">
    Greet
  </button>

  <span data-hello-target="output">
  </span>
</div>

So the first data attribute is controller with the value "hello".

You set that on the outer div which contains the rest of the UI elements.

There are two targets - one for input called name and another for the output.

And there's an action attribute set on the button with the value "click->hello#greet".
 
This means when the button is clicked, call the greet method in the hello controller.

Now, let’s look at the controller code.

// hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

We start by importing the built-in Controller class from Stimulus. We will extend this to define our own hello controller.
import { Controller } from "stimulus"

export default class extends Controller {

Note the naming convention here. The file is called hello_controller.js. That’s the controller name followed by "_controller.js". The name hello is what's used as the identifier value for the data-controller attribute on the container div.

Next we define a list of targets as static properties on the class.
  static targets = [ "name", "output" ]

Targets map HTML elements to controller properties.
We have two targets here - the input name and the output.

Stimulus will automatically create corresponding properties for each of them - this.nameTarget and this.outputTarget respectively.

Again, note the naming convention here. this dot the name of the target followed by the word Target with a capital T.
 
Finally, we have the greet method definition
  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }

The backticks mean it’s a template literal, and the dollar curly brackets ${ } are used to embed an expression within the string. This is similar to string interpolation in Ruby with hash curly brackets #{ }.

Now that we have the basic idea of how Stimulus works, let’s put it to use by building our very own first Stimulus controller.

Instead of coding it from scratch, we can use a Rails generator:
bin/rails generate stimulus gallery

And that will create gallery_controller.js in app/javascript/controllers.

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="gallery"
export default class extends Controller {
  connect() {
  }
}

First we import the Controller class from stimulus and then extend it.
Stimulus has automatically added a method called connect.
This is a special method - a lifecycle callback. There are a few of these special methods. connect runs anytime the controller is connected to the DOM.

To see it in action, let’s add a console log statement that says image gallery controller connected.
connect() {
  console.log('image gallery controller connected')
}

Then we need to define a data-controller attribute so Stimulus knows which DOM element to connect this controller to.

Let’s go to the product partial (app/views/products/_product.html.erb) and add a data controller attribute to the outermost div. And set its value to gallery.
  <div class="min-h-screen py-12 sm:pt-20" data-controller="gallery">

Now refresh the page in the browser and we see our log message in the console.

connected.png 75.52 KB
Next, let’s set the data-action attribute on the thumbnails. Because that’s what we want to click. We set the action to a method called “display”.
<%= image_tag image, class: "thumbnail", data: {action: "click->gallery#display"} %>

Ok, now let’s define this display method in the controller. First, let’s simply console log a message “display clicked image”. So we know our action attribute is working correctly.
display() {
  console.log('display clicked image')
}

Let’s test it in the browser. Refresh and click a thumbnail. And there we have our second console log message.

display.png 99.77 KB
Ok now, we need one more data attribute - a target.

That would be the big image at the top of the gallery. So inside that image tag, let's add data “gallery-target” and we’ll call it “display”
<%= image_tag product.images.first, id: "displayed-image", 
              data: { "gallery-target": "display" } %>

If we refresh and look at the elements in the console, we can see the data attributes we’ve set.

Ok, now let’s proceed with defining the rest of the controller.

First, let’s define our static targets. We only need one here called “display”:
static targets = ["display"]

And finally, let’s define the logic of the display method. Let’s remove the console log statement.

We want to set the value of the src attribute of the display target image.

So we can write:
display() {
  this.displayTarget.src = event.currentTarget.src
}

Remember the naming convention here - target name “display” followed by the word Target with a capital T.
We can get the src of the thumbnail image via “event” i.e. the click event which we used in the data-action attribute. 
currentTarget is the DOM target on which the data-action attribute is defined. In this case, it’ll be whichever thumbnail image is clicked.

Ok, now let’s try that in the browser. Refresh and click a different thumbnail And there you go - the big displayed image changes to that one. Try clicking another one and it works.

gallery.gif 242.93 KB
So there you have it - we have created our first Stimulus controller. We were able to create an image gallery with very little code!