Jese Leos

Stimulus Controllers with a Single Target

For Stimulus controllers with a single target, I have been defaulting to using this.Element instead of requiring a target.

However, some don't like this approach since your controller essentially becomes hardcoded to your markup.

I was updating the footer of this site earlier today and decided to use the following pattern:

  static targets = ['footer']
  footerElement() {
    return (this.hasFooterTarget && this.footerTarget) || this.element
  }
  1. If the target exists, use it
  2. If there is no target, then use the element.

This feels like a happy middle ground. There is no need for the wasted target declaration, but if the markup ever gets more complicated, no code changes are needed in the controller.

If you are curious, here is the entire controller.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["footer"]

  connect() {
    this.adjustFooter()
    window.addEventListener('resize', this.adjustFooter)
  }

  disconnect() {
    window.removeEventListener('resize', this.adjustFooter)
  }

  footerElement() {
    return (this.hasFooterTarget && this.footerTarget) || this.element
  }

  adjustFooter = () => {
    const footer = this.footerElement()
    if (document.body.offsetHeight <= window.innerHeight) {
      footer.classList.add('fixed', 'bottom-0')
      footer.classList.remove('relative')
    } else {
      footer.classList.add('relative')
      footer.classList.remove('fixed', 'bottom-0')
    }
  }
}

When I initially built the app, I had the footer permanently fixed at the bottom. However, when reading the site, I hated the extra space the footer was taking up being fixed. Now, we get the best of both worlds with the following stimulus controller. On pages with limited content, the footer is fixed at the bottom of the page. The footer goes back to being relative on pages with a full screen of content (who writes more than 200 characters at a time these days....😀).