Tailwind Carousel in Rails 7
Published: 2022-03-15 20:53:11

After being absence for a while, I recently began to reshape my Rails skills.

A basic Rails7-setup now includes TailwindCSS and TurboFrames. 
I designed a simple Website providing a carousel as eye-catcher. Sure – you can copy the code from [Tailwind Elements](https://tailwind-elements.com/docs/standard/components/carousel/), install the `tw-elements` javascript-library and anything runs out of the box.

But that's not very rubyish. Each time you want to change something, you have to touch the source-code.  I like the approach to have a _configuration file_, providing input-data for a helper to create the carousel on the fly.

## What defines a Carousel

As shown in the Cover Photo (which is taken from the [Tailwind Elements Page](https://tailwind-elements.com/docs/standard/components/carousel/), we need
* an image file
* a title
* a short description
* a link to other content

## Configuration in config/carousel.yml

It seems only natural to put anything in a yaml-config-file. Lets create `/config/carousel.yml`

```
---
- :first:
    :link:  first
    :image: dizzy-analyst.png 
    :title: Our amazing Approach
    :text:  Explore our unique Solution for all your Problems.
- :second:
    :link:  option
    :image: dizzy-woman.png 
    :title: Feedback
    :text:  We encourage you to provide critical feedback.

```
The image-files have to be copied into `/app/assets/images`! Its assumed, that the links just point to anchors on the same page. 

## The Controller

Assuming, you have setup a `User`-Resource and want to place the carousel on the `Index`-Page.

```ruby
# app/controller/users_controller.rb

class UsersController < ApplicationController
(...)
 def index
    @users = User.all
    @carousel =  YAML.load_file(Rails.root + 'config/carousel.yml')
  end
```
Its very simple: We define the `@users` the usual way and read the complete yaml-file into `@carousel`.

## Display carousel in the viewfile

The view-file is evenly simple

```ruby
# app/views/users/index.html.erb
<header> {some stuff for the header section} </header>
<%= carousel "testCarousel", @carousel %>

....
```
The hole code is delegated to the `carousel`-helper.

## The code: Carousel-Helper
Essentially the [Tailwind Elements ](https://tailwind-elements.com/docs/standard/components/carousel/)-code is translated to fit into _Rails-content_tag_ elements.

```ruby
# /app/helpers/users_helper.rb

module UsersHelper

  def carusel id,  content
    slides =  content.count

    note = ->(n){ "<h2 class=\"text-4xl\">#{n[:title]}</h2> <p class=\"text-xl\">#{n[:text]}</p> "}

    content_tag( :div, class:  %w( carousel slide carousel-fade carousel-dark  relative ), :"data-bs-ride" => 'carousel', id: id ) do
      content_tag( :div , class: %w( carousel-indicators absolute right-0 bottom-0 left-0 flex justify-center p-0 mb-4 ) ) do
      raw( (0 .. slides).map do | count |
          label =  "Slide #{count + 1}"
          raw(  if count.zero?
           content_tag( :button , nil,  type: 'button', :"data-bs-target" => "##{id}" , :"data-bs-slide-to" => count, :"aria-current" => "true", :"aria-label" => label, class: "active" )
                else
           content_tag( :button , nil,  type: 'button', :"data-bs-target" => "##{id}" , :"data-bs-slide-to" => count, :"aria-label" => label )
                end
             )
        end.join)
      end +
       content_tag( :div, class: %w( carousel-inner relative w-3/2 overflow-hidden ) ) do
        raw( content.map.with_index do | c,index |
          h  = c.values.first
            # first image ist active
            class_attributes = index.zero? ? %w( carousel-item active relative float-left w-full ) : %w( carousel-item relative float-left w-full)
            content_tag( :div, class: class_attributes ) do
              content_tag( :a,  href: "##{h[:link]}" ) do 
              image_tag( h[:image]  , class: %w( block w-full)) +
              content_tag( :div , raw(note[h]), class: %w( carousel-caption hidden sm:block absolute text-center ) )
              end
            end
          end.join)
      end  +
      content_tag( :button, class: %w( carousel-control-prev absolute top-0 bottom-0 flex items-center justify-center p-0 text-center border-0 hover:outline-none hover:no-underline focus:outline-none focus:no-underline left-0 ),
                  type: 'button', :"data-bs-target" => "##{id}", :"data-bs-slide" => "prev") do
                      content_tag( :span, nil, class: %w( carousel-control-prev-icon inline-block bg-no-repeat ), :"aria-hidden" => "true") +
                      content_tag( :span, "Previous", class: "visually-hidden")
                                                                                            end +
      content_tag( :button, class: %w(carousel-control-next absolute top-0 bottom-0 flex items-center justify-center p-0 text-center border-0 hover:outline-none hover:no-underline focus:outline-none focus:no-underline right-0) ,
                  type: 'button', :"data-bs-target" => "##{id}", :"data-bs-slide"=> "next") do
                      content_tag( :span, nil, class: %w( carousel-control-next-icon inline-block bg-no-repeat ), :"aria-hidden" => "true") +
                      content_tag( :span, "Next", class: "visually-hidden")
                                                                                            end
    end


  end
end

```
Et voila, the carousel is displayed in `localhost:3000/users`. 

Further customization happens in the helper-code (by trained personal). The display-format for `title` and  `description`, for example, can be changed in the Lambda in row 3. 

## Conclusion

 The goal was, to separate Content and Function. Its just a matter of editing the `config/carousel.yml' file to update the contents of the carousel.