Stimulus Discourse

Pass avariable to rails partial from controller


#1

Hello fellow developers,

From last 2 days I am trying to find a way how to pass a variable between Stimulus and Rails views, and probably because of lack of experience in Rails I can’t get my head around this problem.

I have a slick gallery, that includes photos of the car ( left hand side ), and in the right column I have a select input field ( first one with the Shwarz inside ). What I want to do is the following:

  1. Change a color
  2. Update loop that renders the photos
  3. Render the photos
  4. Reload slick gallery

So far I have the following progress - gallery:

 <div data-target="colour.gallery" class="slick-slider col-12">
  <% Dir.glob('app/assets/images/car-gallery/color-picker/black/*').map do |path| %>  <-- I want to be able to update the BLACK part of the DIR
    <%= image_tag("car-gallery/color-picker/black/#{File.basename(path)}", class: 'd-block w-100') %> <-- same here obviously
  <% end.reduce(&:+) %>

  <% Dir.glob('app/assets/images/car-gallery/interiors/*').map do |path| %>
    <%= image_tag("car-gallery/interiors/#{File.basename(path)}", class: 'd-block w-100') %>
  <% end.reduce(&:+) %>
</div>

<div class="col-12 mb-1">
  <p class="mb-0 text-size-sm">Fahrzeug enthält Sonderausstattung.</p>
</div>

<div data-target="colour.thumbnails" class="slick-navigation col-12">
  <% Dir.glob('app/assets/images/car-gallery/color-picker/black/*').map do |path| %>
    <div class="slick-navigation-item">
      <%= image_tag("car-gallery/color-picker/black/#{File.basename(path)}", class: 'd-block w-100') %>
    </div>
  <% end.reduce(&:+) %>

  <% Dir.glob('app/assets/images/car-gallery/interiors/*').map do |path| %>
    <div class="slick-navigation-item">
      <%= image_tag("car-gallery/interiors/#{File.basename(path)}", class: 'd-block w-100') %>
    </div>
  <% end.reduce(&:+) %>
</div>

Stimulus controller:

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = ['trigger', 'gallery', 'thumbnails']

  connect() {
    console.log(this.value())
    console.log(this.galleryTarget)
    console.log(this.thumbnailsTarget)
    console.log(this.triggerTarget)
  }

  value() {
    const colours = {
      'schwarz': 'black',
      'weiß': 'white',
      'grau': 'grey',
      'rot': 'red'
    }

    const selected = colours[$(this.triggerTarget).val().toLowerCase()]

    console.log(selected)
    return selected === undefined ? 'black' : selected
  }
}

How is it possible to pass a value that is returned inside value() method and pass it to the loop, and re render that part?

Of course there is a great possibility that I am overthinking the problem… I can always just add plain image_tags with data attributes. However it doesn’t feel as a very DRY solution, especially that In order to make a gallery in Slick.js I need to synchronise 2 carousels, with the same content inside, which creates the need of adding the same content twice.


#2

I think the simplest solution would be to make your color picker select box a Stimulus action and use the selected value to update the view accordingly. If you have a template like this:

<div data-controller="car-gallery">
  <select data-action="car-gallery#showCurrentGallery">
    <option value="red">Red</option>   
    <option value="green">Green</option>
    <option value="blue">Blue</option>
  </select>
</div>

You can access the selected color inside the action:

// car_gallery_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
  showCurrentGallery(event) {
    event.currentTarget.value // "red", "green" or "blue" 
  }
}

How you update the view depends on your specific use case. You can either handle it all in the client or use server-side rendering to fetch new galleries.

Client-side
To solve the problem in the client, render all possible galleries at once and use your Stimulus controller to show/hide them when the color is changed. I’ve made a simplified solution here to get you started:

Code: https://glitch.com/edit/#!/stimulus-car-gallery
Live preview: https://stimulus-car-gallery.glitch.me/

Server-side
To solve the problem with server-side rendering, fetch the appropriate gallery via XHR and replace the old one.

Ensure you have a Rails controller action that can render a gallery for a specific color. Something like this:

# car_galleries_controller.rb
class CarGalleriesController < ApplicationController
  def show
    # TODO: Validate the color from params before passing it on
    render partial: 'car_gallery', locals: {color: params[:color]}, layout: false
  end
end

With a matching endpoint in the routes table:

# config/routes.rb
get 'gallery/:color', to: 'car_galleries#show'

Set the endpoint as a data-attribute on the controller element in the template:

<div data-controller="car-gallery" data-car-gallery-url="/gallery">
  <select data-action="car-gallery#showCurrentGallery" data-target="car-gallery.picker">
    <!-- ... -->
  </select>
  <div data-target="car-gallery.container">
    <!-- ... -->
  </div>
</div>

Fetch the new gallery inside the Stimulus action and insert it into the DOM:

// car_callery_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
  static get targets() {
    return ["container", "picker"]
  }

  initialize() {
    this.showCurrentGallery()
  }

  showCurrentGallery() {
    fetch(this.urlForCurrentGallery)
      .then(response => response.text())
      .then(html => this.containerTarget.innerHTML = html)
      .then(/* Do what you need to do with Slick.js here */)
  }

  get urlForCurrentGallery() {
    return `${this.data.get("url")}/${this.pickerTarget.value}`
  }
}

Let me know how it works out :slight_smile:


#3

Thanks a lot for the time spent over such detailed answer. Today I am getting back to that task, I will keep you posted about the final outcome