Search code examples
ruby-on-railsstimulusjs

Stimulus JS error when toggling class from another element


I'm building a transaction entry form to better understand Stimulus after the Odin Project lesson (https://www.theodinproject.com/lessons/ruby-on-rails-stimulus). 1. When the user is finished entering the amount, the next button is clicked and the numpad is hidden (including the 'next' button). 2. If the user would like to edit the amount, a click on the 'amount_box' toggles the hidden state of the numpad.

I've completed the first part, but getting an error on the second part.

JS (toggle_controller.js)

export default class extends Controller {
  static classes = [ "change" ]
  toggle() {
    this.element.classList.toggle(this.changeClass)
  }
}

HTML

<div id="amount_container" >
  <div id="amount_box">
    <div>$999,999.99</div>
  </div>
  <div id="numpad" data-controller="toggle"
                   data-toggle-change-class='hidden'>
    <div>1</div>
    <div>2</div>
    <div>3</div>
...
    <div data-action="click->toggle#toggle">Next</div>
  </div>
</div>

This hides the id="numpad". But once hidden it can't be clicked to un-hide. In that case I would click the id="amount_box". So I moved the data-controller attribute to id='amount_container', which contains both the element being clicked and being toggled.

The updated HTML below throws an error "Error: Missing attribute "data-toggle-change-class" ... element: div#amount_container". It wants to see the data-toggle-change-class on the same element which has data-controller="toggle" but putting it on div#amount_container would just hide the whole thing which is my whole problem in the first place.

<div id="amount_container" data-controller="toggle">
  <div id="amount_box">
    <div>$999,999.99</div>
  </div>
  <div id="numpad" data-toggle-change-class='hidden'>
    <div>1</div>
    <div>2</div>
    <div>3</div>
...
    <div data-action="click->toggle#toggle">Next</div>
  </div>
</div>

How can I click on "Next" or "div#amount_box, and hide/unhide div#numpad?


Solution

  • Currently, your toggle_controller.js toggles the classlist of this.element

    export default class extends Controller {
      static classes = [ "change" ]
      toggle() {
        this.element.classList.toggle(this.changeClass)
      }
    }
    

    this.element refers to the element on which the data-controller attribute is placed. By moving the data-controller from div id="numpad" to <div id="amount_container">, this.element changes scope.

    The solution is to define a stimulus target attribute to specify which element to toggle.

    export default class extends Controller {
      static targets = [ "numpad" ]
      static classes = [ "change" ]
    
      toggle() {
        this.numpadTarget.classList.toggle(this.changeClass)
      }
    }
    
    <div id="amount_container" data-controller="toggle" data-toggle-change-class='hidden'>
      <div id="amount_box">
        <div>$999,999.99</div>
      </div>
      <div id="numpad" data-toggle-target="numpad">
        <div>1</div>
        <div>2</div>
        <div>3</div>
    ...
        <div data-action="click->toggle#toggle">Next</div>
      </div>
    </div>