The Problem
Carousels are a fantastic way of showcasing media in an app. For a long time, implementing this in Rails was rather difficult. Thanks to StimulusJS (which in my own words, gives Rails apps superpowers), we can easily build this feature out.
Please follow along to learn how to go about this.
The Solution
First off, your resource needs to allow for multiple image uploads. Assuming you have a resource named Article
, you would navigate to the article.rb
model and include:
class Article < ApplicationRecord
# Associations
has_many_attached :photos
# Optional validation
validates :photos,
blob: { content_type: %w[image/png image/jpg image/jpeg],
size_range: 0.1..5.megabytes }
end
You can read more on Active Record associations here.
After setting up the association, you will want to allow the resource attribute to be saved in the model. We do this in the articles_conttroller.rb
:
def article_params
params.require(:post).permit(:title, :description, :photos => [])
end
In the article form, you want to allow users to upload the multiple images by adding the following:
<div class="form-field">
<%= form.label :photos %>
<%= form.file_field :photos, :multiple => true %>
</div>
From there, we need to install Stimulus Carousel:
yarn add stimulus-carousel
In your app/javascript/controllers/index.js
, you will need to import and register the component for it to work in your app: **
import Carousel from 'stimulus-carousel'
application.register('carousel', Carousel)
The carousel component uses SwiperJS underneath the hood. For this reason, you will have to import Swiper’s CSS in your app/javascript/packs/application.js
. I personally have included the application.html.erb
layout:
<script src="https://unpkg.com/swiper/swiper-bundle.min.js"></script>
<!-- Initialize Swiper -->
<script>
varswiper= new Swiper('.swiper-container', {
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
});
</script>
I am not looking for much customization of the Swiper stuff.
When all this has been set up, you will need to navigate to the show (app/views/articles/show.html.erb
):
<% if @article.photos.attached? %>
<div data-controller="carousel" class="swiper swiper-initialized swiper-horizontal swiper-pointer-events"
data-carousel-options-value="{ "navigation": { "nextEl": ".swiper-button-next", "prevEl": ".swiper-button-prev" } }" >
<div class="swiper-wrapper">
<% @article.photos.each do |photo| %>
<div class="swiper-slide swiper-slide-prev flex items-center justify-center" style="width: 992px;">
<%= link_to url_for(photo) do %>
<%= link_to url_for(photo) do %>
<%= image_tag url_for(photo), :alt => "#{photo.filename}", :class => "w-fit flex justify-center items-center" %>
<% end %>
<% end %>
</div>
<% end %>
</div>
<%# Swiper Buttons %>
<div class="swiper-button-next" tabindex="0" role="button" aria-label="Next slide" aria-controls="swiper-wrapper-3614d810d7f2d3fb5" aria-disabled="false"></div>
<div class="swiper-button-prev" tabindex="0" role="button" aria-label="Previous slide" aria-controls="swiper-wrapper-3614d810d7f2d3fb5" aria-disabled="false"></div>
<span class="swiper-notification" aria-live="assertive" aria-atomic="true"></span>
</div>
<% end %>
And there you have it, a working carousel with multiple uploads.