Multiple image uploads with Carousel in Rails App - Stimulus

Multiple image uploads with Carousel in Rails App - Stimulus

A tutorial on how to achieve multiple image upload in Ruby on Rails apps

2022-11-10T00:00:00.000Z


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="{ &quot;navigation&quot;: { &quot;nextEl&quot;: &quot;.swiper-button-next&quot;, &quot;prevEl&quot;: &quot;.swiper-button-prev&quot; } }" >
    <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.