Outlets and Permanent Tags

Episode #494 by Teacher's Avatar David Kimura

Summary

In this episode, we'll explore how we can add a "global" music player that will persist across different pages. Our approach will be unobtrusive and implemented in a maintainable way.
rails view stimulusjs outlets 13:45

Chapters

  • Introduction (0:00)
  • Setting up the application (1:32)
  • Talking about different options (3:28)
  • Creating the stimulus controllers (4:26)
  • Creating the Music Player view (4:55)
  • Creating the play button view (7:00)
  • Setting up the Music Player stimulus controller (9:17)
  • Setting up the Track stimulus controller (9:58)
  • Demo (11:21)
  • Organizing the controllers in a different way (11:46)
  • Demo with alternative approach (12:31)
  • Final Thoughts (13:07)

Resources

Source Code - https://github.com/driftingruby/494-outlets-and-permanent-tags

Honeybadger is a performance monitoring and error tracking tool that combines the best monitoring features into one simple interface that works with all the frameworks you use and comes with fantastic support from a small team of passionate developers. With error tracking, performance and uptime monitoring, log management, dashboards, and more, Honeybadger has everything you need to gain real-time insights into the health of your applications.Keep your apps healthy and your customers happy with Honeybadger! It’s free to get started, and setup takes less than five minutes. Start monitoring today →

Download Source Code

Summary

# Terminal
rails action_text:install
rails g scaffold post title content:rich_text
rails g scaffold music title file:attachment
rails g stimulus track
rails g stimulus music_player

# app/views/musics/index.html.erb
<%# audio_tag url_for(music.file), controls: true if music.file.attached? && music.file.audio? %>
<% if music.file.attached? && music.file.audio? %>
  <div data-controller="track"
    data-track-music-player-outlet="#musicPlayer"
    data-audio-url="<%= url_for(music.file) %>"
    data-audio-title="<%= music.title %>">
  <button data-action="click->track#play:prevent" class="text-blue-600 hover:text-blue-800">
    ▶ Play
  </button>
<% end %>

# app/views/layouts/application.html.erb
<div data-controller="music-player" id="musicPlayer" data-turbo-permanent class="fixed bottom-0 left-0 right-0 bg-gray-900 text-white p-4 flex items-center justify-between hidden">
  <span data-music-player-target="title" class="text-lg font-semibold"></span>
  <audio data-music-player-target="audio" controls class="w-full mx-4"><audio>
<div>

# app/javascript/controllers/music_player_controller.js
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="music-player"
export default class extends Controller {
  static targets = ["audio", "title"]

  // Option 1
  // playMusic() {
  //   this.audioTarget.play()
  //   this.element.classList.remove("hidden")
  // }

  playMusic(source, title) {
    this.audioTarget.src = source
    this.titleTarget.textContent = title
    this.audioTarget.play()
    this.element.classList.remove("hidden")
  }
}

# app/javascript/controllers/track_controller.js
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="track"
export default class extends Controller {
  static outlets = ["music-player"]

  play() {
    // Option 1
    // this.musicPlayerOutlet.audioTarget.src = this.element.dataset.audioUrl
    // this.musicPlayerOutlet.titleTarget.textContent = this.element.dataset.audioTitle
    // this.musicPlayerOutlet.playMusic()

    this.musicPlayerOutlet.playMusic(
      this.element.dataset.audioUrl,
      this.element.dataset.audioTitle
    )
  }
}